From b260fee7877f3fea971dbaa39aef0c5c227c78a7 Mon Sep 17 00:00:00 2001 From: miconis Date: Wed, 22 Jul 2020 17:29:48 +0200 Subject: [PATCH 001/445] implementation of the dedup_id generation using pids to make the graph more stable --- .../eu/dnetlib/dhp/oa/dedup/DatePicker.java | 2 +- .../dhp/oa/dedup/DedupRecordFactory.java | 96 ++++++++++++++++--- .../eu/dnetlib/dhp/oa/dedup/Identifier.java | 55 +++++++++++ .../java/eu/dnetlib/dhp/oa/dedup/PidType.java | 54 +++++++++++ .../dhp/oa/dedup/EntityMergerTest.java | 27 +++++- .../dnetlib/dhp/oa/dedup/SparkDedupTest.java | 41 ++++---- .../dhp/dedup/json/publication_merge3.json | 3 + 7 files changed, 237 insertions(+), 41 deletions(-) create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/Identifier.java create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/PidType.java create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge3.json diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DatePicker.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DatePicker.java index 70fb2cc5b..d6b8bf71c 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DatePicker.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DatePicker.java @@ -114,7 +114,7 @@ public class DatePicker { } } - private static boolean inRange(final String date) { + public static boolean inRange(final String date) { final int year = Integer.parseInt(substringBefore(date, "-")); return year >= YEAR_LB && year <= YEAR_UB; } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java index 8028d5a94..7f04afe8a 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java @@ -1,11 +1,12 @@ package eu.dnetlib.dhp.oa.dedup; -import java.io.Serializable; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; - +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Lists; +import eu.dnetlib.dhp.schema.common.ModelSupport; +import eu.dnetlib.dhp.schema.oaf.*; +import org.apache.commons.lang.StringUtils; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.api.java.function.MapGroupsFunction; import org.apache.spark.sql.Dataset; @@ -13,15 +14,12 @@ import org.apache.spark.sql.Encoders; import org.apache.spark.sql.SparkSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.Lists; - -import eu.dnetlib.dhp.schema.common.ModelSupport; -import eu.dnetlib.dhp.schema.oaf.*; import scala.Tuple2; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + public class DedupRecordFactory { private static final Logger log = LoggerFactory.getLogger(DedupRecordFactory.class); @@ -77,15 +75,23 @@ public class DedupRecordFactory { String id, Iterator> entities, long ts, DataInfo dataInfo, Class clazz) throws IllegalAccessException, InstantiationException { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + T entity = clazz.newInstance(); final Collection dates = Lists.newArrayList(); final List> authors = Lists.newArrayList(); + final List bestPids = Lists.newArrayList(); //best pids list entities .forEachRemaining( t -> { T duplicate = t._2(); + + StructuredProperty bestPid = bestPid(duplicate.getPid()); + if (bestPid != null) + bestPids.add(new Identifier(bestPid, extractDate(duplicate, sdf), PidType.classidValueOf(bestPid.getQualifier().getClassid()))); + entity.mergeFrom(duplicate); if (ModelSupport.isSubClass(duplicate, Result.class)) { Result r1 = (Result) duplicate; @@ -94,6 +100,7 @@ public class DedupRecordFactory { if (r1.getDateofacceptance() != null) dates.add(r1.getDateofacceptance().getValue()); } + }); // set authors and date @@ -102,10 +109,73 @@ public class DedupRecordFactory { ((Result) entity).setAuthor(AuthorMerger.merge(authors)); } - entity.setId(id); + Identifier bestPid = winnerPid(bestPids); + if (bestPid == null) + entity.setId(id); + else + entity.setId(id.split("\\|")[0] + "|" + createPrefix(bestPid.getPid().getQualifier().getClassid()) + "::" + DedupUtility.md5(bestPid.getPid().getValue())); + entity.setLastupdatetimestamp(ts); entity.setDataInfo(dataInfo); return entity; } + + //pick the best pid from the list (consider date and pidtype) + public static Identifier winnerPid(List pids) { + if (pids == null || pids.size() == 0) + return null; + Optional bp = pids.stream() + .filter(pid -> pid.getType() != PidType.undefined) + .max(Identifier::compareTo); + return bp.orElse(null); + } + + //pick the best pid from the entity + public static StructuredProperty bestPid(List pids) { + + if (pids == null || pids.size() == 0) + return null; + Optional bp = pids.stream() + .filter(pid -> PidType.classidValueOf(pid.getQualifier().getClassid()) != PidType.undefined) + .max(Comparator.comparing(pid -> PidType.classidValueOf(pid.getQualifier().getClassid()))); + + return bp.orElse(null); + } + + //create the prefix (length = 12): dedup_+ pidType + public static String createPrefix(String pidType) { + + StringBuilder prefix = new StringBuilder("dedup_" + pidType); + + while (prefix.length() < 12) { + prefix.append("_"); + } + return prefix.toString().substring(0, 12); + + } + + //extracts the date from the record. If the date is not available or is not wellformed, it returns a base date: 00-01-01 + public static Date extractDate(T duplicate, SimpleDateFormat sdf){ + + String date = "2000-01-01"; + if (ModelSupport.isSubClass(duplicate, Result.class)) { + Result result = (Result) duplicate; + if (isWellformed(result.getDateofacceptance())){ + date = result.getDateofacceptance().getValue(); + } + } + + try { + return sdf.parse(date); + } catch (ParseException e) { + return new Date(); + } + + } + + public static boolean isWellformed(Field date) { + return date != null && StringUtils.isNotBlank(date.getValue()) && date.getValue().matches("\\d{4}-\\d{2}-\\d{2}") && DatePicker.inRange(date.getValue()); + } + } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/Identifier.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/Identifier.java new file mode 100644 index 000000000..3d92900ca --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/Identifier.java @@ -0,0 +1,55 @@ +package eu.dnetlib.dhp.oa.dedup; + +import eu.dnetlib.dhp.schema.oaf.StructuredProperty; + +import java.io.Serializable; +import java.util.Date; + +public class Identifier implements Serializable, Comparable{ + + StructuredProperty pid; + Date date; + PidType type; + + public Identifier(StructuredProperty pid, Date date, PidType type) { + this.pid = pid; + this.date = date; + this.type = type; + } + + public StructuredProperty getPid() { + return pid; + } + + public void setPid(StructuredProperty pidValue) { + this.pid = pid; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public PidType getType() { + return type; + } + + public void setType(PidType type) { + this.type = type; + } + + @Override + public int compareTo(Identifier i) { + //priority in comparisons: 1) pidtype, 2) date + if (this.getType().compareTo(i.getType()) == 0){ //same type + return this.getDate().compareTo(date); + } + else { + return this.getType().compareTo(i.getType()); + } + + } +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/PidType.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/PidType.java new file mode 100644 index 000000000..c0af44b1e --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/PidType.java @@ -0,0 +1,54 @@ +package eu.dnetlib.dhp.oa.dedup; + +public enum PidType { + + //from the less to the more important + undefined, + orcid, + ror, + grid, + pdb, + arXiv, + pmid, + doi; + + public static PidType classidValueOf(String s){ + try { + return PidType.valueOf(s); + } + catch (Exception e) { + return PidType.undefined; + } + } + +} + +//dnet:pid_types +//"actrn" +//"nct" +//"euctr" +//"epo_id" +//"gsk" +//"GeoPass" +//"GBIF" +//"isrctn" +//"ISNI" +//"jprn" +//"mag_id" +//"oai" +//"orcid" +//"PANGAEA" +//"epo_nr_epodoc" +//"UNKNOWN" +//"VIAF" +//"arXiv" +//"doi" +//"grid" +//"info:eu-repo/dai" +//"orcidworkid" +//"pmc" +//"pmid" +//"urn" +//"who" +//"drks" +//"pdb" \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java index 3fb9d1751..b3cb7b9d9 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java @@ -22,10 +22,11 @@ public class EntityMergerTest implements Serializable { List> publications; List> publications2; + List> publications3; String testEntityBasePath; DataInfo dataInfo; - String dedupId = "dedup_id"; + String dedupId = "00|dedup_id::1"; Publication pub_top; @BeforeEach @@ -38,6 +39,8 @@ public class EntityMergerTest implements Serializable { publications = readSample(testEntityBasePath + "/publication_merge.json", Publication.class); publications2 = readSample(testEntityBasePath + "/publication_merge2.json", Publication.class); + publications3 = readSample(testEntityBasePath + "/publication_merge3.json", Publication.class); + pub_top = getTopPub(publications); @@ -54,6 +57,9 @@ public class EntityMergerTest implements Serializable { .entityMerger(dedupId, softwares.iterator(), 0, dataInfo, Software.class); assertEquals(merged.getBestaccessright().getClassid(), "OPEN SOURCE"); + + assertEquals(merged.getId(), "00|dedup_doi___::0968af610a356656706657e4f234b340"); + } @Test @@ -62,7 +68,8 @@ public class EntityMergerTest implements Serializable { Publication pub_merged = DedupRecordFactory .entityMerger(dedupId, publications.iterator(), 0, dataInfo, Publication.class); - assertEquals(dedupId, pub_merged.getId()); + // verify id + assertEquals(pub_merged.getId(), "00|dedup_doi___::0968af610a356656706657e4f234b340"); assertEquals(pub_merged.getJournal(), pub_top.getJournal()); assertEquals(pub_merged.getBestaccessright(), pub_top.getBestaccessright()); @@ -117,11 +124,25 @@ public class EntityMergerTest implements Serializable { Publication pub_merged = DedupRecordFactory .entityMerger(dedupId, publications2.iterator(), 0, dataInfo, Publication.class); + // verify id + assertEquals(pub_merged.getId(), "00|dedup_doi___::0ca46ff10b2b4c756191719d85302b14"); + assertEquals(pub_merged.getAuthor().size(), 27); - // insert assertions here } + @Test + public void publicationMergerTest3() throws InstantiationException, IllegalAccessException { + + Publication pub_merged = DedupRecordFactory + .entityMerger(dedupId, publications3.iterator(), 0, dataInfo, Publication.class); + + // verify id + assertEquals(pub_merged.getId(), "00|dedup_doi___::0ca46ff10b2b4c756191719d85302b14"); + + } + + public DataInfo setDI() { DataInfo dataInfo = new DataInfo(); dataInfo.setTrust("0.9"); diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java index fb5ebc099..35c4c7026 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java @@ -1,22 +1,12 @@ package eu.dnetlib.dhp.oa.dedup; -import static java.nio.file.Files.createTempDirectory; - -import static org.apache.spark.sql.functions.col; -import static org.apache.spark.sql.functions.count; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.lenient; - -import java.io.File; -import java.io.IOException; -import java.io.Serializable; -import java.net.URISyntaxException; -import java.nio.file.Paths; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Collectors; - +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import eu.dnetlib.pace.util.MapDocumentUtil; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; @@ -35,16 +25,19 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.schema.oaf.Relation; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import eu.dnetlib.pace.util.MapDocumentUtil; import scala.Tuple2; +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.net.URISyntaxException; +import java.nio.file.Paths; + +import static java.nio.file.Files.createTempDirectory; +import static org.apache.spark.sql.functions.count; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.lenient; + @ExtendWith(MockitoExtension.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class SparkDedupTest implements Serializable { diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge3.json b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge3.json new file mode 100644 index 000000000..c82c8c83e --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge3.json @@ -0,0 +1,3 @@ +{"id":"50|doajarticles::842fa3b99fcdccafb4d5c8a815f56efa","dateofcollection":"2020-04-06T12:22:31.216Z","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Korean Continence Society"},"bestaccessright":null,"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":null,"key":"10|driver______::bee53aa31dc2cbb538c10c2b65fa5824","value":"DOAJ-Articles"}],"pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"doi","classname":"doi","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"10.5213/inj.1632552.276"}, {"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"doi","classname":"doi","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"10.5213/inj.1632552.27XXXXX"}],"author":[{"affiliation":null,"fullname":"Seok Joong Yun","name":null,"pid":[],"rank":1,"surname":null},{"affiliation":null,"fullname":"Pildu Jeong","name":null,"pid":[],"rank":2,"surname":null},{"affiliation":null,"fullname":"Ho Won Kang","name":null,"pid":[],"rank":3,"surname":null},{"affiliation":null,"fullname":"Helen Ki Shinn","name":null,"pid":[],"rank":4,"surname":null},{"affiliation":null,"fullname":"Ye-Hwan Kim","name":null,"pid":[],"rank":5,"surname":null},{"affiliation":null,"fullname":"Chunri Yan","name":null,"pid":[],"rank":6,"surname":null},{"affiliation":null,"fullname":"Young-Ki Choi","name":null,"pid":[],"rank":7,"surname":null},{"affiliation":null,"fullname":"Dongho Kim","name":null,"pid":[],"rank":8,"surname":null},{"affiliation":null,"fullname":"Dong Hee Ryu","name":null,"pid":[],"rank":9,"surname":null},{"affiliation":null,"fullname":"Yun-Sok Ha","name":null,"pid":[],"rank":10,"surname":null},{"affiliation":null,"fullname":"Tae-Hwan Kim","name":null,"pid":[],"rank":11,"surname":null},{"affiliation":null,"fullname":"Tae Gyun Kwon","name":null,"pid":[],"rank":12,"surname":null},{"affiliation":null,"fullname":"Jung Min Kim","name":null,"pid":[],"rank":13,"surname":null},{"affiliation":null,"fullname":"Sang Heon Suh","name":null,"pid":[],"rank":14,"surname":null},{"affiliation":null,"fullname":"Seon-Kyu Kim","name":null,"pid":[],"rank":15,"surname":null},{"affiliation":null,"fullname":"Seon-Young Kim","name":null,"pid":[],"rank":16,"surname":null},{"affiliation":null,"fullname":"Sang Tae Kim","name":null,"pid":[],"rank":17,"surname":null},{"affiliation":null,"fullname":"Won Tae Kim","name":null,"pid":[],"rank":18,"surname":null},{"affiliation":null,"fullname":"Ok-Jun Lee","name":null,"pid":[],"rank":19,"surname":null},{"affiliation":null,"fullname":"Sung-Kwon Moon","name":null,"pid":[],"rank":20,"surname":null},{"affiliation":null,"fullname":"Nam-Hyung Kim","name":null,"pid":[],"rank":21,"surname":null},{"affiliation":null,"fullname":"Isaac Yi Kim","name":null,"pid":[],"rank":22,"surname":null},{"affiliation":null,"fullname":"Jayoung Kim","name":null,"pid":[],"rank":23,"surname":null},{"affiliation":null,"fullname":"Hee-Jae Cha","name":null,"pid":[],"rank":24,"surname":null},{"affiliation":null,"fullname":"Yung-Hyun Choi","name":null,"pid":[],"rank":25,"surname":null},{"affiliation":null,"fullname":"Eun-Jong Cha","name":null,"pid":[],"rank":26,"surname":null},{"affiliation":null,"fullname":"Wun-Jae Kim","name":null,"pid":[],"rank":27,"surname":null}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"eng","classname":"English","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"MicroRNAs"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Neoplasms"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Herpesviridae"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Hyperplasia"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Diseases of the genitourinary system. Urology"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"RC870-923"}],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Purpose: Previously, we reported the presence of virus-encoded microRNAs (miRNAs) in the urine of prostate cancer (CaP) patients. In this study, we investigated the expression of two herpes virus-encoded miRNAs in prostate tissue. Methods: A total of 175 tissue samples from noncancerous benign prostatic hyperplasia (BPH), 248 tissue samples from patients with CaP and BPH, and 50 samples from noncancerous surrounding tissues from these same patients were analyzed for the expression of two herpes virus-encoded miRNAs by real-time polymerase chain reaction (PCR) and immunocytochemistry using nanoparticles as molecular beacons. Results: Real-time reverse transcription-PCR results revealed significantly higher expression of hsv1-miR-H18 and hsv2-miRH9- 5p in surrounding noncancerous and CaP tissues than that in BPH tissue (each comparison, P<0.001). Of note, these miRNA were expressed equivalently in the CaP tissues and surrounding noncancerous tissues. Moreover, immunocytochemistry clearly demonstrated a significant enrichment of both hsv1-miR-H18 and hsv2-miR-H9 beacon-labeled cells in CaP and surrounding noncancerous tissue compared to that in BPH tissue (each comparison, P<0.05 for hsv1-miR-H18 and hsv2- miR-H9). Conclusions: These results suggest that increased expression of hsv1-miR-H18 and hsv2-miR-H95p might be associated with tumorigenesis in the prostate. Further studies will be required to elucidate the role of these miRNAs with respect to CaP and herpes viral infections."}],"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"embargoenddate":null,"resourcetype":null,"context":[],"instance":[{"accessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"collectedfrom":{"dataInfo":null,"key":"10|driver______::bee53aa31dc2cbb538c10c2b65fa5824","value":"DOAJ-Articles"},"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"distributionlocation":"","hostedby":{"dataInfo":null,"key":"10|doajarticles::52db9a4f8e176f6e8e1d9f0c1e0a2de0","value":"International Neurourology Journal"},"instancetype":{"classid":"0001","classname":"peerReviewed","schemeid":"dnet:publication_resource","schemename":"dnet:publication_resource"},"license":null,"processingchargeamount":null,"processingchargecurrency":null,"refereed":null,"url":["http://www.einj.org/upload/pdf/inj-1632552-276.pdf","https://doaj.org/toc/2093-4777","https://doaj.org/toc/2093-6931"]}]} +{"id":"50|od_______267::b5f5da11a8239ef57655cea8675cb466","dateofcollection":"","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Korean Continence Society"},"bestaccessright":null,"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":null,"key":"10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357","value":"PubMed Central"}],"pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"doi","classname":"doi","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"10.5213/inj.1632552.276"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"pmc","classname":"pmc","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"PMC4932644"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"pmid","classname":"pmid","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"27377944"}],"author":[{"affiliation":null,"fullname":"Yun, Seok Joong","name":"Seok Joong","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-7737-4746"}],"rank":1,"surname":"Yun"},{"affiliation":null,"fullname":"Jeong, Pildu","name":"Pildu","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-5602-5376"}],"rank":2,"surname":"Jeong"},{"affiliation":null,"fullname":"Kang, Ho Won","name":"Ho Won","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8164-4427"}],"rank":3,"surname":"Kang"},{"affiliation":null,"fullname":"Shinn, Helen Ki","name":"Helen Ki","pid":[],"rank":4,"surname":"Shinn"},{"affiliation":null,"fullname":"Kim, Ye-Hwan","name":"Ye-Hwan","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8676-7119"}],"rank":5,"surname":"Kim"},{"affiliation":null,"fullname":"Yan, Chunri","name":"Chunri","pid":[],"rank":6,"surname":"Yan"},{"affiliation":null,"fullname":"Choi, Young-Ki","name":"Young-Ki","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1894-9869"}],"rank":7,"surname":"Choi"},{"affiliation":null,"fullname":"Kim, Dongho","name":"Dongho","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1409-3311"}],"rank":8,"surname":"Kim"},{"affiliation":null,"fullname":"Ryu, Dong Hee","name":"Dong Hee","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-6088-298X"}],"rank":9,"surname":"Ryu"},{"affiliation":null,"fullname":"Ha, Yun-Sok","name":"Yun-Sok","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0003-3732-9814"}],"rank":10,"surname":"Ha"},{"affiliation":null,"fullname":"Kim, Tae-Hwan","name":"Tae-Hwan","pid":[],"rank":11,"surname":"Kim"},{"affiliation":null,"fullname":"Kwon, Tae Gyun","name":"Tae Gyun","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-4390-0952"}],"rank":12,"surname":"Kwon"},{"affiliation":null,"fullname":"Kim, Jung Min","name":"Jung Min","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-6319-0217"}],"rank":13,"surname":"Kim"},{"affiliation":null,"fullname":"Suh, Sang Heon","name":"Sang Heon","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0003-4560-8880"}],"rank":14,"surname":"Suh"},{"affiliation":null,"fullname":"Kim, Seon-Kyu","name":"Seon-Kyu","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-4176-5187"}],"rank":15,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Seon-Young","name":"Seon-Young","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1030-7730"}],"rank":16,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Sang Tae","name":"Sang Tae","pid":[],"rank":17,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Won Tae","name":"Won Tae","pid":[],"rank":18,"surname":"Kim"},{"affiliation":null,"fullname":"Lee, Ok-Jun","name":"Ok-Jun","pid":[],"rank":19,"surname":"Lee"},{"affiliation":null,"fullname":"Moon, Sung-Kwon","name":"Sung-Kwon","pid":[],"rank":20,"surname":"Moon"},{"affiliation":null,"fullname":"Kim, Nam-Hyung","name":"Nam-Hyung","pid":[],"rank":21,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Isaac Yi","name":"Isaac Yi","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1967-5281"}],"rank":22,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Jayoung","name":"Jayoung","pid":[],"rank":23,"surname":"Kim"},{"affiliation":null,"fullname":"Cha, Hee-Jae","name":"Hee-Jae","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-6963-2685"}],"rank":24,"surname":"Cha"},{"affiliation":null,"fullname":"Choi, Yung-Hyun","name":"Yung-Hyun","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1454-3124"}],"rank":25,"surname":"Choi"},{"affiliation":null,"fullname":"Cha, Eun-Jong","name":"Eun-Jong","pid":[],"rank":26,"surname":"Cha"},{"affiliation":null,"fullname":"Kim, Wun-Jae","name":"Wun-Jae","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8060-8926"}],"rank":27,"surname":"Kim"}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"eng","classname":"English","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Original Article"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Fundamental Science for Neurourology"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"MicroRNAs"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Neoplasms"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Herpesviridae"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Hyperplasia"}],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Purpose: Previously, we reported the presence of virus-encoded microRNAs (miRNAs) in the urine of prostate cancer (CaP) patients. In this study, we investigated the expression of two herpes virus-encoded miRNAs in prostate tissue. Methods: A total of 175 tissue samples from noncancerous benign prostatic hyperplasia (BPH), 248 tissue samples from patients with CaP and BPH, and 50 samples from noncancerous surrounding tissues from these same patients were analyzed for the expression of two herpes virus-encoded miRNAs by real-time polymerase chain reaction (PCR) and immunocytochemistry using nanoparticles as molecular beacons. Results: Real-time reverse transcription-PCR results revealed significantly higher expression of hsv1-miR-H18 and hsv2-miRH9- 5p in surrounding noncancerous and CaP tissues than that in BPH tissue (each comparison, P<0.001). Of note, these miRNA were expressed equivalently in the CaP tissues and surrounding noncancerous tissues. Moreover, immunocytochemistry clearly demonstrated a significant enrichment of both hsv1-miR-H18 and hsv2-miR-H9 beacon-labeled cells in CaP and surrounding noncancerous tissue compared to that in BPH tissue (each comparison, P<0.05 for hsv1-miR-H18 and hsv2- miR-H9). Conclusions: These results suggest that increased expression of hsv1-miR-H18 and hsv2-miR-H95p might be associated with tumorigenesis in the prostate. Further studies will be required to elucidate the role of these miRNAs with respect to CaP and herpes viral infections."}],"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"embargoenddate":null,"resourcetype":null,"context":[],"instance":[{"accessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"collectedfrom":{"dataInfo":null,"key":"10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357","value":"PubMed Central"},"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"distributionlocation":"","hostedby":{"dataInfo":null,"key":"10|opendoar____::8b6dd7db9af49e67306feb59a8bdc52c","value":"Europe PubMed Central"},"instancetype":{"classid":"0001","classname":"peerReviewed","schemeid":"dnet:publication_resource","schemename":"dnet:publication_resource"},"license":null,"processingchargeamount":null,"processingchargecurrency":null,"refereed":null,"url":["http://europepmc.org/articles/PMC4932644"]}]} +{"id":"50|doiboost____::0ca46ff10b2b4c756191719d85302b14","dateofcollection":"2019-02-15","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":""},"bestaccessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:actionset","classname":"sysimport:actionset","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::081b82f96300b6a6e3d282bad31cb6e2","value":"Crossref"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::5f532a3fc4f1ea403f37070f59a7a53a","value":"Microsoft Academic Graph"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::8ac8380272269217cb09a928c8caa993","value":"UnpayWall"}],"pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"qualifier":{"classid":"doi","classname":"doi","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"10.5213/inj.1632552.276"}],"author":[{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Seok Joong Yun","name":"Seok Joong","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2105974574"}],"rank":1,"surname":"Yun"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Pildu Jeong","name":"Pildu","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2041919263"}],"rank":2,"surname":"Jeong"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ho Won Kang","name":"Ho Won","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2164408067"}],"rank":3,"surname":"Kang"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Inha University"}],"fullname":"Helen Ki Shinn","name":"Helen Ki","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2045077081"}],"rank":4,"surname":"Shinn"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ye-Hwan Kim","name":"Ye-Hwan","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2276303457"}],"rank":5,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Chunri Yan","name":"Chunri","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2186750404"}],"rank":6,"surname":"Yan"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Young-Ki Choi","name":"Young-Ki","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2311466124"}],"rank":7,"surname":"Choi"},{"affiliation":[],"fullname":"Dongho Kim","name":"Dongho","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2644843893"}],"rank":8,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Dong Hee Ryu","name":"Dong Hee","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2117604941"}],"rank":9,"surname":"Ryu"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Yun-Sok Ha","name":"Yun-Sok","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2145233282"}],"rank":10,"surname":"Ha"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Tae-Hwan Kim","name":"Tae-Hwan","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2509096378"}],"rank":11,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Tae Gyun Kwon","name":"Tae Gyun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"1978978081"}],"rank":12,"surname":"Kwon"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Daejeon University"}],"fullname":"Jung Min Kim","name":"Jung Min","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2265841962"}],"rank":13,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"KAIST"}],"fullname":"Sang Heon Suh","name":"Sang Heon","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2890693470"}],"rank":14,"surname":"Suh"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Korea Research Institute of Bioscience and Biotechnology"}],"fullname":"Seon-Kyu Kim","name":"Seon-Kyu","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2162364977"}],"rank":15,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Korea Research Institute of Bioscience and Biotechnology"}],"fullname":"Seon-Young Kim","name":"Seon-Young","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2344797375"}],"rank":16,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Seoul National University Bundang Hospital"}],"fullname":"Sang Tae Kim","name":"Sang Tae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2257827509"}],"rank":17,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Won Tae Kim","name":"Won Tae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2617237649"}],"rank":18,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ok-Jun Lee","name":"Ok-Jun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2112231548"}],"rank":19,"surname":"Lee"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chung-Ang University"}],"fullname":"Sung-Kwon Moon","name":"Sung-Kwon","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2796689429"}],"rank":20,"surname":"Moon"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Nam-Hyung Kim","name":"Nam-Hyung","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2136287741"}],"rank":21,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Rutgers University"}],"fullname":"Isaac Yi Kim","name":"Isaac Yi","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2015295992"}],"rank":22,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Harvard University"}],"fullname":"Jayoung Kim","name":"Jayoung","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2130848131"}],"rank":23,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kosin University"}],"fullname":"Hee-Jae Cha","name":"Hee-Jae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2113489867"}],"rank":24,"surname":"Cha"},{"affiliation":[],"fullname":"Yung-Hyun Choi","name":"Yung-Hyun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2151282194"}],"rank":25,"surname":"Choi"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Eun-Jong Cha","name":"Eun-Jong","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2109572239"}],"rank":26,"surname":"Cha"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Wun-Jae Kim","name":"Wun-Jae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2113339670"}],"rank":27,"surname":"Kim"}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"und","classname":"Undetermined","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Purpose:"}],"dateofacceptance":null,"embargoenddate":null,"resourcetype":null,"context":null,"instance":null} \ No newline at end of file From d47352cbc7daf9d5c20a92c97953c15b1aff19c0 Mon Sep 17 00:00:00 2001 From: miconis Date: Fri, 24 Jul 2020 20:10:47 +0200 Subject: [PATCH 002/445] refactoring of the procedure for the id generation, minor changes and addition of a comparation on the original id and the origin datasource --- .../eu/dnetlib/dhp/oa/dedup/DatePicker.java | 2 +- .../dhp/oa/dedup/DedupRecordFactory.java | 70 +-------------- .../eu/dnetlib/dhp/oa/dedup/IdGenerator.java | 90 +++++++++++++++++++ .../eu/dnetlib/dhp/oa/dedup/Identifier.java | 83 ++++++++++++++++- .../java/eu/dnetlib/dhp/oa/dedup/PidType.java | 33 +------ .../dhp/oa/dedup/EntityMergerTest.java | 34 +++++-- .../dhp/dedup/json/publication_merge4.json | 3 + .../dhp/dedup/json/publication_merge5.json | 3 + 8 files changed, 212 insertions(+), 106 deletions(-) create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge4.json create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge5.json diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DatePicker.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DatePicker.java index d6b8bf71c..c2fe09a4d 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DatePicker.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DatePicker.java @@ -18,7 +18,7 @@ import eu.dnetlib.dhp.schema.oaf.Field; public class DatePicker { - private static final String DATE_PATTERN = "\\d{4}-\\d{2}-\\d{2}"; + public static final String DATE_PATTERN = "\\d{4}-\\d{2}-\\d{2}"; private static final String DATE_DEFAULT_SUFFIX = "01-01"; private static final int YEAR_LB = 1300; private static final int YEAR_UB = Year.now().getValue() + 5; diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java index 7f04afe8a..0fc393ea5 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java @@ -4,6 +4,7 @@ package eu.dnetlib.dhp.oa.dedup; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; +import eu.dnetlib.dhp.schema.common.EntityType; import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.schema.oaf.*; import org.apache.commons.lang.StringUtils; @@ -75,8 +76,6 @@ public class DedupRecordFactory { String id, Iterator> entities, long ts, DataInfo dataInfo, Class clazz) throws IllegalAccessException, InstantiationException { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - T entity = clazz.newInstance(); final Collection dates = Lists.newArrayList(); @@ -88,9 +87,8 @@ public class DedupRecordFactory { t -> { T duplicate = t._2(); - StructuredProperty bestPid = bestPid(duplicate.getPid()); - if (bestPid != null) - bestPids.add(new Identifier(bestPid, extractDate(duplicate, sdf), PidType.classidValueOf(bestPid.getQualifier().getClassid()))); + //prepare the list of pids to use for the id generation + bestPids.addAll(IdGenerator.bestPidtoIdentifier(duplicate)); entity.mergeFrom(duplicate); if (ModelSupport.isSubClass(duplicate, Result.class)) { @@ -109,11 +107,7 @@ public class DedupRecordFactory { ((Result) entity).setAuthor(AuthorMerger.merge(authors)); } - Identifier bestPid = winnerPid(bestPids); - if (bestPid == null) - entity.setId(id); - else - entity.setId(id.split("\\|")[0] + "|" + createPrefix(bestPid.getPid().getQualifier().getClassid()) + "::" + DedupUtility.md5(bestPid.getPid().getValue())); + entity.setId(IdGenerator.generate(bestPids, id)); entity.setLastupdatetimestamp(ts); entity.setDataInfo(dataInfo); @@ -121,61 +115,5 @@ public class DedupRecordFactory { return entity; } - //pick the best pid from the list (consider date and pidtype) - public static Identifier winnerPid(List pids) { - if (pids == null || pids.size() == 0) - return null; - Optional bp = pids.stream() - .filter(pid -> pid.getType() != PidType.undefined) - .max(Identifier::compareTo); - return bp.orElse(null); - } - - //pick the best pid from the entity - public static StructuredProperty bestPid(List pids) { - - if (pids == null || pids.size() == 0) - return null; - Optional bp = pids.stream() - .filter(pid -> PidType.classidValueOf(pid.getQualifier().getClassid()) != PidType.undefined) - .max(Comparator.comparing(pid -> PidType.classidValueOf(pid.getQualifier().getClassid()))); - - return bp.orElse(null); - } - - //create the prefix (length = 12): dedup_+ pidType - public static String createPrefix(String pidType) { - - StringBuilder prefix = new StringBuilder("dedup_" + pidType); - - while (prefix.length() < 12) { - prefix.append("_"); - } - return prefix.toString().substring(0, 12); - - } - - //extracts the date from the record. If the date is not available or is not wellformed, it returns a base date: 00-01-01 - public static Date extractDate(T duplicate, SimpleDateFormat sdf){ - - String date = "2000-01-01"; - if (ModelSupport.isSubClass(duplicate, Result.class)) { - Result result = (Result) duplicate; - if (isWellformed(result.getDateofacceptance())){ - date = result.getDateofacceptance().getValue(); - } - } - - try { - return sdf.parse(date); - } catch (ParseException e) { - return new Date(); - } - - } - - public static boolean isWellformed(Field date) { - return date != null && StringUtils.isNotBlank(date.getValue()) && date.getValue().matches("\\d{4}-\\d{2}-\\d{2}") && DatePicker.inRange(date.getValue()); - } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java new file mode 100644 index 000000000..2d203a1b1 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java @@ -0,0 +1,90 @@ +package eu.dnetlib.dhp.oa.dedup; + +import com.google.common.collect.Lists; +import eu.dnetlib.dhp.schema.common.EntityType; +import eu.dnetlib.dhp.schema.common.ModelSupport; +import eu.dnetlib.dhp.schema.oaf.Field; +import eu.dnetlib.dhp.schema.oaf.OafEntity; +import eu.dnetlib.dhp.schema.oaf.Result; +import eu.dnetlib.dhp.schema.oaf.StructuredProperty; +import org.apache.commons.lang.NullArgumentException; +import org.apache.commons.lang.StringUtils; + +import java.io.Serializable; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +public class IdGenerator implements Serializable { + + private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + public static String CROSSREF_ID = "10|openaire____::081b82f96300b6a6e3d282bad31cb6e2"; + public static String DATACITE_ID = "10|openaire____::9e3be59865b2c1c335d32dae2fe7b254"; + + //pick the best pid from the list (consider date and pidtype) + public static String generate(List pids, String defaultID) { + if (pids == null || pids.size() == 0) + return defaultID; + + Optional bp = pids.stream() + .max(Identifier::compareTo); + + if (bp.get().isUseOriginal() || bp.get().getPid().getValue() == null) { + return bp.get().getOriginalID().split("\\|")[0] + "|dedup_wf_001::" + DedupUtility.md5(bp.get().getOriginalID()); + } else { + return bp.get().getOriginalID().split("\\|")[0] + "|" + createPrefix(bp.get().getPid().getQualifier().getClassid()) + "::" + DedupUtility.md5(bp.get().getPid().getValue()); + } + + } + + //pick the best pid from the entity. Returns a list (length 1) to save time in the call + public static List bestPidtoIdentifier(T entity) { + + if (entity.getPid() == null || entity.getPid().size() == 0) + return Lists.newArrayList(new Identifier(new StructuredProperty(), new Date(), PidType.original, entity.getCollectedfrom(), EntityType.fromClass(entity.getClass()), entity.getId())); + + Optional bp = entity.getPid().stream() + .filter(pid -> PidType.classidValueOf(pid.getQualifier().getClassid()) != PidType.undefined) + .max(Comparator.comparing(pid -> PidType.classidValueOf(pid.getQualifier().getClassid()))); + + return bp.map(structuredProperty -> + Lists.newArrayList(new Identifier(structuredProperty, extractDate(entity, sdf), PidType.classidValueOf(structuredProperty.getQualifier().getClassid()), entity.getCollectedfrom(), EntityType.fromClass(entity.getClass()), entity.getId())) + ).orElseGet(() -> Lists.newArrayList(new Identifier(new StructuredProperty(), new Date(), PidType.original, entity.getCollectedfrom(), EntityType.fromClass(entity.getClass()), entity.getId()))); + + } + + //create the prefix (length = 12): dedup_+ pidType + public static String createPrefix(String pidType) { + + StringBuilder prefix = new StringBuilder("dedup_" + pidType); + + while (prefix.length() < 12) { + prefix.append("_"); + } + return prefix.toString().substring(0, 12); + + } + + //extracts the date from the record. If the date is not available or is not wellformed, it returns a base date: 00-01-01 + public static Date extractDate(T duplicate, SimpleDateFormat sdf){ + + String date = "2000-01-01"; + if (ModelSupport.isSubClass(duplicate, Result.class)) { + Result result = (Result) duplicate; + if (isWellformed(result.getDateofacceptance())){ + date = result.getDateofacceptance().getValue(); + } + } + + try { + return sdf.parse(date); + } catch (ParseException e) { + return new Date(); + } + + } + + public static boolean isWellformed(Field date) { + return date != null && StringUtils.isNotBlank(date.getValue()) && date.getValue().matches(DatePicker.DATE_PATTERN) && DatePicker.inRange(date.getValue()); + } +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/Identifier.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/Identifier.java index 3d92900ca..fd52d20f9 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/Identifier.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/Identifier.java @@ -1,20 +1,31 @@ package eu.dnetlib.dhp.oa.dedup; +import eu.dnetlib.dhp.schema.common.EntityType; +import eu.dnetlib.dhp.schema.oaf.KeyValue; import eu.dnetlib.dhp.schema.oaf.StructuredProperty; import java.io.Serializable; import java.util.Date; +import java.util.List; public class Identifier implements Serializable, Comparable{ StructuredProperty pid; Date date; PidType type; + List collectedFrom; + EntityType entityType; + String originalID; - public Identifier(StructuredProperty pid, Date date, PidType type) { + boolean useOriginal = false; //to know if the top identifier won because of the alphabetical order of the original ID + + public Identifier(StructuredProperty pid, Date date, PidType type, List collectedFrom, EntityType entityType, String originalID) { this.pid = pid; this.date = date; this.type = type; + this.collectedFrom = collectedFrom; + this.entityType = entityType; + this.originalID = originalID; } public StructuredProperty getPid() { @@ -41,15 +52,81 @@ public class Identifier implements Serializable, Comparable{ this.type = type; } + public List getCollectedFrom() { + return collectedFrom; + } + + public void setCollectedFrom(List collectedFrom) { + this.collectedFrom = collectedFrom; + } + + public EntityType getEntityType() { + return entityType; + } + + public void setEntityType(EntityType entityType) { + this.entityType = entityType; + } + + public String getOriginalID() { + return originalID; + } + + public void setOriginalID(String originalID) { + this.originalID = originalID; + } + + public boolean isUseOriginal() { + return useOriginal; + } + + public void setUseOriginal(boolean useOriginal) { + this.useOriginal = useOriginal; + } + @Override public int compareTo(Identifier i) { - //priority in comparisons: 1) pidtype, 2) date + //priority in comparisons: 1) pidtype, 2) collectedfrom (depending on the entity type) , 3) date 4) alphabetical order of the originalID if (this.getType().compareTo(i.getType()) == 0){ //same type - return this.getDate().compareTo(date); + if (entityType == EntityType.publication) { + if (isFromDatasourceID(this.collectedFrom, IdGenerator.CROSSREF_ID) && !isFromDatasourceID(i.collectedFrom, IdGenerator.CROSSREF_ID)) + return 1; + if (isFromDatasourceID(i.collectedFrom, IdGenerator.CROSSREF_ID) && !isFromDatasourceID(this.collectedFrom, IdGenerator.CROSSREF_ID)) + return -1; + } + if (entityType == EntityType.dataset) { + if (isFromDatasourceID(this.collectedFrom, IdGenerator.DATACITE_ID) && !isFromDatasourceID(i.collectedFrom, IdGenerator.DATACITE_ID)) + return 1; + if (isFromDatasourceID(i.collectedFrom, IdGenerator.DATACITE_ID) && !isFromDatasourceID(this.collectedFrom, IdGenerator.DATACITE_ID)) + return -1; + } + + if (this.getDate().compareTo(date) == 0) {//same date + + if (this.originalID.compareTo(i.originalID) > 0) + this.useOriginal = true; + else + i.setUseOriginal(true); + + //the minus because we need to take the alphabetically lower id + return -this.originalID.compareTo(i.originalID); + } + else + //the minus is because we need to take the elder date + return -this.getDate().compareTo(date); } else { return this.getType().compareTo(i.getType()); } } + + public boolean isFromDatasourceID(List collectedFrom, String dsId){ + + for(KeyValue cf: collectedFrom) { + if(cf.getKey().equals(dsId)) + return true; + } + return false; + } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/PidType.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/PidType.java index c0af44b1e..ab5c49868 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/PidType.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/PidType.java @@ -4,6 +4,7 @@ public enum PidType { //from the less to the more important undefined, + original, orcid, ror, grid, @@ -21,34 +22,4 @@ public enum PidType { } } -} - -//dnet:pid_types -//"actrn" -//"nct" -//"euctr" -//"epo_id" -//"gsk" -//"GeoPass" -//"GBIF" -//"isrctn" -//"ISNI" -//"jprn" -//"mag_id" -//"oai" -//"orcid" -//"PANGAEA" -//"epo_nr_epodoc" -//"UNKNOWN" -//"VIAF" -//"arXiv" -//"doi" -//"grid" -//"info:eu-repo/dai" -//"orcidworkid" -//"pmc" -//"pmid" -//"urn" -//"who" -//"drks" -//"pdb" \ No newline at end of file +} \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java index b3cb7b9d9..431751584 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java @@ -23,6 +23,8 @@ public class EntityMergerTest implements Serializable { List> publications; List> publications2; List> publications3; + List> publications4; + List> publications5; String testEntityBasePath; DataInfo dataInfo; @@ -40,7 +42,8 @@ public class EntityMergerTest implements Serializable { publications = readSample(testEntityBasePath + "/publication_merge.json", Publication.class); publications2 = readSample(testEntityBasePath + "/publication_merge2.json", Publication.class); publications3 = readSample(testEntityBasePath + "/publication_merge3.json", Publication.class); - + publications4 = readSample(testEntityBasePath + "/publication_merge4.json", Publication.class); + publications5 = readSample(testEntityBasePath + "/publication_merge5.json", Publication.class); pub_top = getTopPub(publications); @@ -58,7 +61,7 @@ public class EntityMergerTest implements Serializable { assertEquals(merged.getBestaccessright().getClassid(), "OPEN SOURCE"); - assertEquals(merged.getId(), "00|dedup_doi___::0968af610a356656706657e4f234b340"); + assertEquals(merged.getId(), "50|dedup_doi___::0968af610a356656706657e4f234b340"); } @@ -69,7 +72,7 @@ public class EntityMergerTest implements Serializable { .entityMerger(dedupId, publications.iterator(), 0, dataInfo, Publication.class); // verify id - assertEquals(pub_merged.getId(), "00|dedup_doi___::0968af610a356656706657e4f234b340"); + assertEquals(pub_merged.getId(), "50|dedup_doi___::0968af610a356656706657e4f234b340"); assertEquals(pub_merged.getJournal(), pub_top.getJournal()); assertEquals(pub_merged.getBestaccessright(), pub_top.getBestaccessright()); @@ -125,7 +128,7 @@ public class EntityMergerTest implements Serializable { .entityMerger(dedupId, publications2.iterator(), 0, dataInfo, Publication.class); // verify id - assertEquals(pub_merged.getId(), "00|dedup_doi___::0ca46ff10b2b4c756191719d85302b14"); + assertEquals("50|dedup_doi___::0ca46ff10b2b4c756191719d85302b14", pub_merged.getId()); assertEquals(pub_merged.getAuthor().size(), 27); @@ -138,10 +141,31 @@ public class EntityMergerTest implements Serializable { .entityMerger(dedupId, publications3.iterator(), 0, dataInfo, Publication.class); // verify id - assertEquals(pub_merged.getId(), "00|dedup_doi___::0ca46ff10b2b4c756191719d85302b14"); + assertEquals( "50|dedup_doi___::0ca46ff10b2b4c756191719d85302b14", pub_merged.getId()); } + @Test + public void publicationMergerTest4() throws InstantiationException, IllegalStateException, IllegalAccessException { + + Publication pub_merged = DedupRecordFactory + .entityMerger(dedupId, publications4.iterator(), 0, dataInfo, Publication.class); + + // verify id + assertEquals("50|dedup_wf_001::2d2bbbbcfb285e3fb3590237b79e2fa8", pub_merged.getId()); + + } + + @Test + public void publicationMergerTest5() throws InstantiationException, IllegalStateException, IllegalAccessException { + + Publication pub_merged = DedupRecordFactory + .entityMerger(dedupId, publications5.iterator(), 0, dataInfo, Publication.class); + + // verify id + assertEquals("50|dedup_wf_001::584b89679c3ccd1015b647ec63cc2699", pub_merged.getId()); + + } public DataInfo setDI() { DataInfo dataInfo = new DataInfo(); diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge4.json b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge4.json new file mode 100644 index 000000000..785465ae9 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge4.json @@ -0,0 +1,3 @@ +{"id":"50|doajarticles::842fa3b99fcdccafb4d5c8a815f56efa","dateofcollection":"2020-04-06T12:22:31.216Z","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Korean Continence Society"},"bestaccessright":null,"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":null,"key":"10|driver______::bee53aa31dc2cbb538c10c2b65fa5824","value":"DOAJ-Articles"}],"pid":[],"author":[{"affiliation":null,"fullname":"Seok Joong Yun","name":null,"pid":[],"rank":1,"surname":null},{"affiliation":null,"fullname":"Pildu Jeong","name":null,"pid":[],"rank":2,"surname":null},{"affiliation":null,"fullname":"Ho Won Kang","name":null,"pid":[],"rank":3,"surname":null},{"affiliation":null,"fullname":"Helen Ki Shinn","name":null,"pid":[],"rank":4,"surname":null},{"affiliation":null,"fullname":"Ye-Hwan Kim","name":null,"pid":[],"rank":5,"surname":null},{"affiliation":null,"fullname":"Chunri Yan","name":null,"pid":[],"rank":6,"surname":null},{"affiliation":null,"fullname":"Young-Ki Choi","name":null,"pid":[],"rank":7,"surname":null},{"affiliation":null,"fullname":"Dongho Kim","name":null,"pid":[],"rank":8,"surname":null},{"affiliation":null,"fullname":"Dong Hee Ryu","name":null,"pid":[],"rank":9,"surname":null},{"affiliation":null,"fullname":"Yun-Sok Ha","name":null,"pid":[],"rank":10,"surname":null},{"affiliation":null,"fullname":"Tae-Hwan Kim","name":null,"pid":[],"rank":11,"surname":null},{"affiliation":null,"fullname":"Tae Gyun Kwon","name":null,"pid":[],"rank":12,"surname":null},{"affiliation":null,"fullname":"Jung Min Kim","name":null,"pid":[],"rank":13,"surname":null},{"affiliation":null,"fullname":"Sang Heon Suh","name":null,"pid":[],"rank":14,"surname":null},{"affiliation":null,"fullname":"Seon-Kyu Kim","name":null,"pid":[],"rank":15,"surname":null},{"affiliation":null,"fullname":"Seon-Young Kim","name":null,"pid":[],"rank":16,"surname":null},{"affiliation":null,"fullname":"Sang Tae Kim","name":null,"pid":[],"rank":17,"surname":null},{"affiliation":null,"fullname":"Won Tae Kim","name":null,"pid":[],"rank":18,"surname":null},{"affiliation":null,"fullname":"Ok-Jun Lee","name":null,"pid":[],"rank":19,"surname":null},{"affiliation":null,"fullname":"Sung-Kwon Moon","name":null,"pid":[],"rank":20,"surname":null},{"affiliation":null,"fullname":"Nam-Hyung Kim","name":null,"pid":[],"rank":21,"surname":null},{"affiliation":null,"fullname":"Isaac Yi Kim","name":null,"pid":[],"rank":22,"surname":null},{"affiliation":null,"fullname":"Jayoung Kim","name":null,"pid":[],"rank":23,"surname":null},{"affiliation":null,"fullname":"Hee-Jae Cha","name":null,"pid":[],"rank":24,"surname":null},{"affiliation":null,"fullname":"Yung-Hyun Choi","name":null,"pid":[],"rank":25,"surname":null},{"affiliation":null,"fullname":"Eun-Jong Cha","name":null,"pid":[],"rank":26,"surname":null},{"affiliation":null,"fullname":"Wun-Jae Kim","name":null,"pid":[],"rank":27,"surname":null}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"eng","classname":"English","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"MicroRNAs"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Neoplasms"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Herpesviridae"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Hyperplasia"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Diseases of the genitourinary system. Urology"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"RC870-923"}],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Purpose: Previously, we reported the presence of virus-encoded microRNAs (miRNAs) in the urine of prostate cancer (CaP) patients. In this study, we investigated the expression of two herpes virus-encoded miRNAs in prostate tissue. Methods: A total of 175 tissue samples from noncancerous benign prostatic hyperplasia (BPH), 248 tissue samples from patients with CaP and BPH, and 50 samples from noncancerous surrounding tissues from these same patients were analyzed for the expression of two herpes virus-encoded miRNAs by real-time polymerase chain reaction (PCR) and immunocytochemistry using nanoparticles as molecular beacons. Results: Real-time reverse transcription-PCR results revealed significantly higher expression of hsv1-miR-H18 and hsv2-miRH9- 5p in surrounding noncancerous and CaP tissues than that in BPH tissue (each comparison, P<0.001). Of note, these miRNA were expressed equivalently in the CaP tissues and surrounding noncancerous tissues. Moreover, immunocytochemistry clearly demonstrated a significant enrichment of both hsv1-miR-H18 and hsv2-miR-H9 beacon-labeled cells in CaP and surrounding noncancerous tissue compared to that in BPH tissue (each comparison, P<0.05 for hsv1-miR-H18 and hsv2- miR-H9). Conclusions: These results suggest that increased expression of hsv1-miR-H18 and hsv2-miR-H95p might be associated with tumorigenesis in the prostate. Further studies will be required to elucidate the role of these miRNAs with respect to CaP and herpes viral infections."}],"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"embargoenddate":null,"resourcetype":null,"context":[],"instance":[{"accessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"collectedfrom":{"dataInfo":null,"key":"10|driver______::bee53aa31dc2cbb538c10c2b65fa5824","value":"DOAJ-Articles"},"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"distributionlocation":"","hostedby":{"dataInfo":null,"key":"10|doajarticles::52db9a4f8e176f6e8e1d9f0c1e0a2de0","value":"International Neurourology Journal"},"instancetype":{"classid":"0001","classname":"peerReviewed","schemeid":"dnet:publication_resource","schemename":"dnet:publication_resource"},"license":null,"processingchargeamount":null,"processingchargecurrency":null,"refereed":null,"url":["http://www.einj.org/upload/pdf/inj-1632552-276.pdf","https://doaj.org/toc/2093-4777","https://doaj.org/toc/2093-6931"]}]} +{"id":"50|od_______267::b5f5da11a8239ef57655cea8675cb466","dateofcollection":"","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Korean Continence Society"},"bestaccessright":null,"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":null,"key":"10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357","value":"PubMed Central"}],"pid":[],"author":[{"affiliation":null,"fullname":"Yun, Seok Joong","name":"Seok Joong","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-7737-4746"}],"rank":1,"surname":"Yun"},{"affiliation":null,"fullname":"Jeong, Pildu","name":"Pildu","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-5602-5376"}],"rank":2,"surname":"Jeong"},{"affiliation":null,"fullname":"Kang, Ho Won","name":"Ho Won","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8164-4427"}],"rank":3,"surname":"Kang"},{"affiliation":null,"fullname":"Shinn, Helen Ki","name":"Helen Ki","pid":[],"rank":4,"surname":"Shinn"},{"affiliation":null,"fullname":"Kim, Ye-Hwan","name":"Ye-Hwan","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8676-7119"}],"rank":5,"surname":"Kim"},{"affiliation":null,"fullname":"Yan, Chunri","name":"Chunri","pid":[],"rank":6,"surname":"Yan"},{"affiliation":null,"fullname":"Choi, Young-Ki","name":"Young-Ki","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1894-9869"}],"rank":7,"surname":"Choi"},{"affiliation":null,"fullname":"Kim, Dongho","name":"Dongho","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1409-3311"}],"rank":8,"surname":"Kim"},{"affiliation":null,"fullname":"Ryu, Dong Hee","name":"Dong Hee","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-6088-298X"}],"rank":9,"surname":"Ryu"},{"affiliation":null,"fullname":"Ha, Yun-Sok","name":"Yun-Sok","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0003-3732-9814"}],"rank":10,"surname":"Ha"},{"affiliation":null,"fullname":"Kim, Tae-Hwan","name":"Tae-Hwan","pid":[],"rank":11,"surname":"Kim"},{"affiliation":null,"fullname":"Kwon, Tae Gyun","name":"Tae Gyun","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-4390-0952"}],"rank":12,"surname":"Kwon"},{"affiliation":null,"fullname":"Kim, Jung Min","name":"Jung Min","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-6319-0217"}],"rank":13,"surname":"Kim"},{"affiliation":null,"fullname":"Suh, Sang Heon","name":"Sang Heon","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0003-4560-8880"}],"rank":14,"surname":"Suh"},{"affiliation":null,"fullname":"Kim, Seon-Kyu","name":"Seon-Kyu","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-4176-5187"}],"rank":15,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Seon-Young","name":"Seon-Young","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1030-7730"}],"rank":16,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Sang Tae","name":"Sang Tae","pid":[],"rank":17,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Won Tae","name":"Won Tae","pid":[],"rank":18,"surname":"Kim"},{"affiliation":null,"fullname":"Lee, Ok-Jun","name":"Ok-Jun","pid":[],"rank":19,"surname":"Lee"},{"affiliation":null,"fullname":"Moon, Sung-Kwon","name":"Sung-Kwon","pid":[],"rank":20,"surname":"Moon"},{"affiliation":null,"fullname":"Kim, Nam-Hyung","name":"Nam-Hyung","pid":[],"rank":21,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Isaac Yi","name":"Isaac Yi","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1967-5281"}],"rank":22,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Jayoung","name":"Jayoung","pid":[],"rank":23,"surname":"Kim"},{"affiliation":null,"fullname":"Cha, Hee-Jae","name":"Hee-Jae","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-6963-2685"}],"rank":24,"surname":"Cha"},{"affiliation":null,"fullname":"Choi, Yung-Hyun","name":"Yung-Hyun","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1454-3124"}],"rank":25,"surname":"Choi"},{"affiliation":null,"fullname":"Cha, Eun-Jong","name":"Eun-Jong","pid":[],"rank":26,"surname":"Cha"},{"affiliation":null,"fullname":"Kim, Wun-Jae","name":"Wun-Jae","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8060-8926"}],"rank":27,"surname":"Kim"}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"eng","classname":"English","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Original Article"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Fundamental Science for Neurourology"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"MicroRNAs"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Neoplasms"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Herpesviridae"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Hyperplasia"}],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Purpose: Previously, we reported the presence of virus-encoded microRNAs (miRNAs) in the urine of prostate cancer (CaP) patients. In this study, we investigated the expression of two herpes virus-encoded miRNAs in prostate tissue. Methods: A total of 175 tissue samples from noncancerous benign prostatic hyperplasia (BPH), 248 tissue samples from patients with CaP and BPH, and 50 samples from noncancerous surrounding tissues from these same patients were analyzed for the expression of two herpes virus-encoded miRNAs by real-time polymerase chain reaction (PCR) and immunocytochemistry using nanoparticles as molecular beacons. Results: Real-time reverse transcription-PCR results revealed significantly higher expression of hsv1-miR-H18 and hsv2-miRH9- 5p in surrounding noncancerous and CaP tissues than that in BPH tissue (each comparison, P<0.001). Of note, these miRNA were expressed equivalently in the CaP tissues and surrounding noncancerous tissues. Moreover, immunocytochemistry clearly demonstrated a significant enrichment of both hsv1-miR-H18 and hsv2-miR-H9 beacon-labeled cells in CaP and surrounding noncancerous tissue compared to that in BPH tissue (each comparison, P<0.05 for hsv1-miR-H18 and hsv2- miR-H9). Conclusions: These results suggest that increased expression of hsv1-miR-H18 and hsv2-miR-H95p might be associated with tumorigenesis in the prostate. Further studies will be required to elucidate the role of these miRNAs with respect to CaP and herpes viral infections."}],"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"embargoenddate":null,"resourcetype":null,"context":[],"instance":[{"accessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"collectedfrom":{"dataInfo":null,"key":"10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357","value":"PubMed Central"},"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"distributionlocation":"","hostedby":{"dataInfo":null,"key":"10|opendoar____::8b6dd7db9af49e67306feb59a8bdc52c","value":"Europe PubMed Central"},"instancetype":{"classid":"0001","classname":"peerReviewed","schemeid":"dnet:publication_resource","schemename":"dnet:publication_resource"},"license":null,"processingchargeamount":null,"processingchargecurrency":null,"refereed":null,"url":["http://europepmc.org/articles/PMC4932644"]}]} +{"id":"50|doiboost____::0ca46ff10b2b4c756191719d85302b14","dateofcollection":"2019-02-15","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":""},"bestaccessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:actionset","classname":"sysimport:actionset","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::081b82f96300b6a6e3d282bad31cb6e2","value":"Crossref"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::5f532a3fc4f1ea403f37070f59a7a53a","value":"Microsoft Academic Graph"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::8ac8380272269217cb09a928c8caa993","value":"UnpayWall"}],"pid":[],"author":[{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Seok Joong Yun","name":"Seok Joong","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2105974574"}],"rank":1,"surname":"Yun"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Pildu Jeong","name":"Pildu","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2041919263"}],"rank":2,"surname":"Jeong"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ho Won Kang","name":"Ho Won","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2164408067"}],"rank":3,"surname":"Kang"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Inha University"}],"fullname":"Helen Ki Shinn","name":"Helen Ki","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2045077081"}],"rank":4,"surname":"Shinn"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ye-Hwan Kim","name":"Ye-Hwan","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2276303457"}],"rank":5,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Chunri Yan","name":"Chunri","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2186750404"}],"rank":6,"surname":"Yan"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Young-Ki Choi","name":"Young-Ki","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2311466124"}],"rank":7,"surname":"Choi"},{"affiliation":[],"fullname":"Dongho Kim","name":"Dongho","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2644843893"}],"rank":8,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Dong Hee Ryu","name":"Dong Hee","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2117604941"}],"rank":9,"surname":"Ryu"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Yun-Sok Ha","name":"Yun-Sok","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2145233282"}],"rank":10,"surname":"Ha"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Tae-Hwan Kim","name":"Tae-Hwan","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2509096378"}],"rank":11,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Tae Gyun Kwon","name":"Tae Gyun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"1978978081"}],"rank":12,"surname":"Kwon"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Daejeon University"}],"fullname":"Jung Min Kim","name":"Jung Min","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2265841962"}],"rank":13,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"KAIST"}],"fullname":"Sang Heon Suh","name":"Sang Heon","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2890693470"}],"rank":14,"surname":"Suh"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Korea Research Institute of Bioscience and Biotechnology"}],"fullname":"Seon-Kyu Kim","name":"Seon-Kyu","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2162364977"}],"rank":15,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Korea Research Institute of Bioscience and Biotechnology"}],"fullname":"Seon-Young Kim","name":"Seon-Young","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2344797375"}],"rank":16,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Seoul National University Bundang Hospital"}],"fullname":"Sang Tae Kim","name":"Sang Tae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2257827509"}],"rank":17,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Won Tae Kim","name":"Won Tae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2617237649"}],"rank":18,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ok-Jun Lee","name":"Ok-Jun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2112231548"}],"rank":19,"surname":"Lee"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chung-Ang University"}],"fullname":"Sung-Kwon Moon","name":"Sung-Kwon","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2796689429"}],"rank":20,"surname":"Moon"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Nam-Hyung Kim","name":"Nam-Hyung","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2136287741"}],"rank":21,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Rutgers University"}],"fullname":"Isaac Yi Kim","name":"Isaac Yi","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2015295992"}],"rank":22,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Harvard University"}],"fullname":"Jayoung Kim","name":"Jayoung","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2130848131"}],"rank":23,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kosin University"}],"fullname":"Hee-Jae Cha","name":"Hee-Jae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2113489867"}],"rank":24,"surname":"Cha"},{"affiliation":[],"fullname":"Yung-Hyun Choi","name":"Yung-Hyun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2151282194"}],"rank":25,"surname":"Choi"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Eun-Jong Cha","name":"Eun-Jong","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2109572239"}],"rank":26,"surname":"Cha"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Wun-Jae Kim","name":"Wun-Jae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2113339670"}],"rank":27,"surname":"Kim"}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"und","classname":"Undetermined","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Purpose:"}],"dateofacceptance":null,"embargoenddate":null,"resourcetype":null,"context":null,"instance":null} \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge5.json b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge5.json new file mode 100644 index 000000000..416b75a9b --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge5.json @@ -0,0 +1,3 @@ +{"id":"50|doajarticles::842fa3b99fcdccafb4d5c8a815f56efa","dateofcollection":"2020-04-06T12:22:31.216Z","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Korean Continence Society"},"bestaccessright":null,"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":null,"key":"10|driver______::bee53aa31dc2cbb538c10c2b65fa5824","value":"DOAJ-Articles"}],"pid":[],"author":[{"affiliation":null,"fullname":"Seok Joong Yun","name":null,"pid":[],"rank":1,"surname":null},{"affiliation":null,"fullname":"Pildu Jeong","name":null,"pid":[],"rank":2,"surname":null},{"affiliation":null,"fullname":"Ho Won Kang","name":null,"pid":[],"rank":3,"surname":null},{"affiliation":null,"fullname":"Helen Ki Shinn","name":null,"pid":[],"rank":4,"surname":null},{"affiliation":null,"fullname":"Ye-Hwan Kim","name":null,"pid":[],"rank":5,"surname":null},{"affiliation":null,"fullname":"Chunri Yan","name":null,"pid":[],"rank":6,"surname":null},{"affiliation":null,"fullname":"Young-Ki Choi","name":null,"pid":[],"rank":7,"surname":null},{"affiliation":null,"fullname":"Dongho Kim","name":null,"pid":[],"rank":8,"surname":null},{"affiliation":null,"fullname":"Dong Hee Ryu","name":null,"pid":[],"rank":9,"surname":null},{"affiliation":null,"fullname":"Yun-Sok Ha","name":null,"pid":[],"rank":10,"surname":null},{"affiliation":null,"fullname":"Tae-Hwan Kim","name":null,"pid":[],"rank":11,"surname":null},{"affiliation":null,"fullname":"Tae Gyun Kwon","name":null,"pid":[],"rank":12,"surname":null},{"affiliation":null,"fullname":"Jung Min Kim","name":null,"pid":[],"rank":13,"surname":null},{"affiliation":null,"fullname":"Sang Heon Suh","name":null,"pid":[],"rank":14,"surname":null},{"affiliation":null,"fullname":"Seon-Kyu Kim","name":null,"pid":[],"rank":15,"surname":null},{"affiliation":null,"fullname":"Seon-Young Kim","name":null,"pid":[],"rank":16,"surname":null},{"affiliation":null,"fullname":"Sang Tae Kim","name":null,"pid":[],"rank":17,"surname":null},{"affiliation":null,"fullname":"Won Tae Kim","name":null,"pid":[],"rank":18,"surname":null},{"affiliation":null,"fullname":"Ok-Jun Lee","name":null,"pid":[],"rank":19,"surname":null},{"affiliation":null,"fullname":"Sung-Kwon Moon","name":null,"pid":[],"rank":20,"surname":null},{"affiliation":null,"fullname":"Nam-Hyung Kim","name":null,"pid":[],"rank":21,"surname":null},{"affiliation":null,"fullname":"Isaac Yi Kim","name":null,"pid":[],"rank":22,"surname":null},{"affiliation":null,"fullname":"Jayoung Kim","name":null,"pid":[],"rank":23,"surname":null},{"affiliation":null,"fullname":"Hee-Jae Cha","name":null,"pid":[],"rank":24,"surname":null},{"affiliation":null,"fullname":"Yung-Hyun Choi","name":null,"pid":[],"rank":25,"surname":null},{"affiliation":null,"fullname":"Eun-Jong Cha","name":null,"pid":[],"rank":26,"surname":null},{"affiliation":null,"fullname":"Wun-Jae Kim","name":null,"pid":[],"rank":27,"surname":null}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"eng","classname":"English","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"MicroRNAs"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Neoplasms"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Herpesviridae"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Hyperplasia"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Diseases of the genitourinary system. Urology"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"RC870-923"}],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Purpose: Previously, we reported the presence of virus-encoded microRNAs (miRNAs) in the urine of prostate cancer (CaP) patients. In this study, we investigated the expression of two herpes virus-encoded miRNAs in prostate tissue. Methods: A total of 175 tissue samples from noncancerous benign prostatic hyperplasia (BPH), 248 tissue samples from patients with CaP and BPH, and 50 samples from noncancerous surrounding tissues from these same patients were analyzed for the expression of two herpes virus-encoded miRNAs by real-time polymerase chain reaction (PCR) and immunocytochemistry using nanoparticles as molecular beacons. Results: Real-time reverse transcription-PCR results revealed significantly higher expression of hsv1-miR-H18 and hsv2-miRH9- 5p in surrounding noncancerous and CaP tissues than that in BPH tissue (each comparison, P<0.001). Of note, these miRNA were expressed equivalently in the CaP tissues and surrounding noncancerous tissues. Moreover, immunocytochemistry clearly demonstrated a significant enrichment of both hsv1-miR-H18 and hsv2-miR-H9 beacon-labeled cells in CaP and surrounding noncancerous tissue compared to that in BPH tissue (each comparison, P<0.05 for hsv1-miR-H18 and hsv2- miR-H9). Conclusions: These results suggest that increased expression of hsv1-miR-H18 and hsv2-miR-H95p might be associated with tumorigenesis in the prostate. Further studies will be required to elucidate the role of these miRNAs with respect to CaP and herpes viral infections."}],"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"embargoenddate":null,"resourcetype":null,"context":[],"instance":[{"accessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"collectedfrom":{"dataInfo":null,"key":"10|driver______::bee53aa31dc2cbb538c10c2b65fa5824","value":"DOAJ-Articles"},"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"distributionlocation":"","hostedby":{"dataInfo":null,"key":"10|doajarticles::52db9a4f8e176f6e8e1d9f0c1e0a2de0","value":"International Neurourology Journal"},"instancetype":{"classid":"0001","classname":"peerReviewed","schemeid":"dnet:publication_resource","schemename":"dnet:publication_resource"},"license":null,"processingchargeamount":null,"processingchargecurrency":null,"refereed":null,"url":["http://www.einj.org/upload/pdf/inj-1632552-276.pdf","https://doaj.org/toc/2093-4777","https://doaj.org/toc/2093-6931"]}]} +{"id":"50|od_______267::b5f5da11a8239ef57655cea8675cb466","dateofcollection":"","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Korean Continence Society"},"bestaccessright":null,"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":null,"key":"10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357","value":"PubMed Central"}],"pid":[],"author":[{"affiliation":null,"fullname":"Yun, Seok Joong","name":"Seok Joong","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-7737-4746"}],"rank":1,"surname":"Yun"},{"affiliation":null,"fullname":"Jeong, Pildu","name":"Pildu","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-5602-5376"}],"rank":2,"surname":"Jeong"},{"affiliation":null,"fullname":"Kang, Ho Won","name":"Ho Won","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8164-4427"}],"rank":3,"surname":"Kang"},{"affiliation":null,"fullname":"Shinn, Helen Ki","name":"Helen Ki","pid":[],"rank":4,"surname":"Shinn"},{"affiliation":null,"fullname":"Kim, Ye-Hwan","name":"Ye-Hwan","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8676-7119"}],"rank":5,"surname":"Kim"},{"affiliation":null,"fullname":"Yan, Chunri","name":"Chunri","pid":[],"rank":6,"surname":"Yan"},{"affiliation":null,"fullname":"Choi, Young-Ki","name":"Young-Ki","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1894-9869"}],"rank":7,"surname":"Choi"},{"affiliation":null,"fullname":"Kim, Dongho","name":"Dongho","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1409-3311"}],"rank":8,"surname":"Kim"},{"affiliation":null,"fullname":"Ryu, Dong Hee","name":"Dong Hee","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-6088-298X"}],"rank":9,"surname":"Ryu"},{"affiliation":null,"fullname":"Ha, Yun-Sok","name":"Yun-Sok","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0003-3732-9814"}],"rank":10,"surname":"Ha"},{"affiliation":null,"fullname":"Kim, Tae-Hwan","name":"Tae-Hwan","pid":[],"rank":11,"surname":"Kim"},{"affiliation":null,"fullname":"Kwon, Tae Gyun","name":"Tae Gyun","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-4390-0952"}],"rank":12,"surname":"Kwon"},{"affiliation":null,"fullname":"Kim, Jung Min","name":"Jung Min","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-6319-0217"}],"rank":13,"surname":"Kim"},{"affiliation":null,"fullname":"Suh, Sang Heon","name":"Sang Heon","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0003-4560-8880"}],"rank":14,"surname":"Suh"},{"affiliation":null,"fullname":"Kim, Seon-Kyu","name":"Seon-Kyu","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-4176-5187"}],"rank":15,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Seon-Young","name":"Seon-Young","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1030-7730"}],"rank":16,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Sang Tae","name":"Sang Tae","pid":[],"rank":17,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Won Tae","name":"Won Tae","pid":[],"rank":18,"surname":"Kim"},{"affiliation":null,"fullname":"Lee, Ok-Jun","name":"Ok-Jun","pid":[],"rank":19,"surname":"Lee"},{"affiliation":null,"fullname":"Moon, Sung-Kwon","name":"Sung-Kwon","pid":[],"rank":20,"surname":"Moon"},{"affiliation":null,"fullname":"Kim, Nam-Hyung","name":"Nam-Hyung","pid":[],"rank":21,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Isaac Yi","name":"Isaac Yi","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1967-5281"}],"rank":22,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Jayoung","name":"Jayoung","pid":[],"rank":23,"surname":"Kim"},{"affiliation":null,"fullname":"Cha, Hee-Jae","name":"Hee-Jae","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-6963-2685"}],"rank":24,"surname":"Cha"},{"affiliation":null,"fullname":"Choi, Yung-Hyun","name":"Yung-Hyun","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1454-3124"}],"rank":25,"surname":"Choi"},{"affiliation":null,"fullname":"Cha, Eun-Jong","name":"Eun-Jong","pid":[],"rank":26,"surname":"Cha"},{"affiliation":null,"fullname":"Kim, Wun-Jae","name":"Wun-Jae","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8060-8926"}],"rank":27,"surname":"Kim"}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"eng","classname":"English","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Original Article"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Fundamental Science for Neurourology"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"MicroRNAs"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Neoplasms"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Herpesviridae"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Hyperplasia"}],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Purpose: Previously, we reported the presence of virus-encoded microRNAs (miRNAs) in the urine of prostate cancer (CaP) patients. In this study, we investigated the expression of two herpes virus-encoded miRNAs in prostate tissue. Methods: A total of 175 tissue samples from noncancerous benign prostatic hyperplasia (BPH), 248 tissue samples from patients with CaP and BPH, and 50 samples from noncancerous surrounding tissues from these same patients were analyzed for the expression of two herpes virus-encoded miRNAs by real-time polymerase chain reaction (PCR) and immunocytochemistry using nanoparticles as molecular beacons. Results: Real-time reverse transcription-PCR results revealed significantly higher expression of hsv1-miR-H18 and hsv2-miRH9- 5p in surrounding noncancerous and CaP tissues than that in BPH tissue (each comparison, P<0.001). Of note, these miRNA were expressed equivalently in the CaP tissues and surrounding noncancerous tissues. Moreover, immunocytochemistry clearly demonstrated a significant enrichment of both hsv1-miR-H18 and hsv2-miR-H9 beacon-labeled cells in CaP and surrounding noncancerous tissue compared to that in BPH tissue (each comparison, P<0.05 for hsv1-miR-H18 and hsv2- miR-H9). Conclusions: These results suggest that increased expression of hsv1-miR-H18 and hsv2-miR-H95p might be associated with tumorigenesis in the prostate. Further studies will be required to elucidate the role of these miRNAs with respect to CaP and herpes viral infections."}],"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"embargoenddate":null,"resourcetype":null,"context":[],"instance":[{"accessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"collectedfrom":{"dataInfo":null,"key":"10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357","value":"PubMed Central"},"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"distributionlocation":"","hostedby":{"dataInfo":null,"key":"10|opendoar____::8b6dd7db9af49e67306feb59a8bdc52c","value":"Europe PubMed Central"},"instancetype":{"classid":"0001","classname":"peerReviewed","schemeid":"dnet:publication_resource","schemename":"dnet:publication_resource"},"license":null,"processingchargeamount":null,"processingchargecurrency":null,"refereed":null,"url":["http://europepmc.org/articles/PMC4932644"]}]} +{"id":"50|doiboost____::0ca46ff10b2b4c756191719d85302b14","dateofcollection":"2019-02-15","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":""},"bestaccessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:actionset","classname":"sysimport:actionset","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::5f532a3fc4f1ea403f37070f59a7a53a","value":"Microsoft Academic Graph"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::8ac8380272269217cb09a928c8caa993","value":"UnpayWall"}],"pid":[],"author":[{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Seok Joong Yun","name":"Seok Joong","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2105974574"}],"rank":1,"surname":"Yun"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Pildu Jeong","name":"Pildu","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2041919263"}],"rank":2,"surname":"Jeong"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ho Won Kang","name":"Ho Won","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2164408067"}],"rank":3,"surname":"Kang"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Inha University"}],"fullname":"Helen Ki Shinn","name":"Helen Ki","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2045077081"}],"rank":4,"surname":"Shinn"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ye-Hwan Kim","name":"Ye-Hwan","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2276303457"}],"rank":5,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Chunri Yan","name":"Chunri","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2186750404"}],"rank":6,"surname":"Yan"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Young-Ki Choi","name":"Young-Ki","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2311466124"}],"rank":7,"surname":"Choi"},{"affiliation":[],"fullname":"Dongho Kim","name":"Dongho","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2644843893"}],"rank":8,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Dong Hee Ryu","name":"Dong Hee","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2117604941"}],"rank":9,"surname":"Ryu"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Yun-Sok Ha","name":"Yun-Sok","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2145233282"}],"rank":10,"surname":"Ha"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Tae-Hwan Kim","name":"Tae-Hwan","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2509096378"}],"rank":11,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Tae Gyun Kwon","name":"Tae Gyun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"1978978081"}],"rank":12,"surname":"Kwon"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Daejeon University"}],"fullname":"Jung Min Kim","name":"Jung Min","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2265841962"}],"rank":13,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"KAIST"}],"fullname":"Sang Heon Suh","name":"Sang Heon","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2890693470"}],"rank":14,"surname":"Suh"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Korea Research Institute of Bioscience and Biotechnology"}],"fullname":"Seon-Kyu Kim","name":"Seon-Kyu","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2162364977"}],"rank":15,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Korea Research Institute of Bioscience and Biotechnology"}],"fullname":"Seon-Young Kim","name":"Seon-Young","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2344797375"}],"rank":16,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Seoul National University Bundang Hospital"}],"fullname":"Sang Tae Kim","name":"Sang Tae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2257827509"}],"rank":17,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Won Tae Kim","name":"Won Tae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2617237649"}],"rank":18,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ok-Jun Lee","name":"Ok-Jun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2112231548"}],"rank":19,"surname":"Lee"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chung-Ang University"}],"fullname":"Sung-Kwon Moon","name":"Sung-Kwon","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2796689429"}],"rank":20,"surname":"Moon"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Nam-Hyung Kim","name":"Nam-Hyung","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2136287741"}],"rank":21,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Rutgers University"}],"fullname":"Isaac Yi Kim","name":"Isaac Yi","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2015295992"}],"rank":22,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Harvard University"}],"fullname":"Jayoung Kim","name":"Jayoung","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2130848131"}],"rank":23,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kosin University"}],"fullname":"Hee-Jae Cha","name":"Hee-Jae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2113489867"}],"rank":24,"surname":"Cha"},{"affiliation":[],"fullname":"Yung-Hyun Choi","name":"Yung-Hyun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2151282194"}],"rank":25,"surname":"Choi"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Eun-Jong Cha","name":"Eun-Jong","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2109572239"}],"rank":26,"surname":"Cha"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Wun-Jae Kim","name":"Wun-Jae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2113339670"}],"rank":27,"surname":"Kim"}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"und","classname":"Undetermined","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Purpose:"}],"dateofacceptance":null,"embargoenddate":null,"resourcetype":null,"context":null,"instance":null} \ No newline at end of file From 259362ef47d7a2a754fa0d5a8a29902fca6435b8 Mon Sep 17 00:00:00 2001 From: miconis Date: Tue, 22 Sep 2020 09:43:27 +0200 Subject: [PATCH 003/445] implementation of the job to collect simrels from postgres db --- .../dhp/oa/dedup/SparkCollectSimRels.java | 167 ++++++++++++++++++ .../dhp/oa/dedup/SparkCreateMergeRels.java | 16 +- .../dhp/oa/dedup/SparkCreateSimRels.java | 17 +- .../oa/dedup/collectSimRels_parameters.json | 44 +++++ .../dnetlib/dhp/oa/dedup/SparkDedupTest.java | 140 +++++++++++---- .../dhp/dedup/assertions/groups/._SUCCESS.crc | Bin 0 -> 8 bytes ...-9cf4-eae22806175b-c000.snappy.parquet.crc | Bin 0 -> 612 bytes .../dhp/dedup/assertions/groups/_SUCCESS | 0 ...4d26-9cf4-eae22806175b-c000.snappy.parquet | Bin 0 -> 77026 bytes .../similarity_groups/._SUCCESS.crc | Bin 0 -> 8 bytes ...-a215-1619e7bb4e5d-c000.snappy.parquet.crc | Bin 0 -> 3904 bytes .../assertions/similarity_groups/_SUCCESS | 0 ...42f1-a215-1619e7bb4e5d-c000.snappy.parquet | Bin 0 -> 498300 bytes 13 files changed, 332 insertions(+), 52 deletions(-) create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCollectSimRels.java create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/collectSimRels_parameters.json create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/groups/._SUCCESS.crc create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/groups/.part-00000-4bafcd13-3995-4d26-9cf4-eae22806175b-c000.snappy.parquet.crc create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/groups/_SUCCESS create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/groups/part-00000-4bafcd13-3995-4d26-9cf4-eae22806175b-c000.snappy.parquet create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/similarity_groups/._SUCCESS.crc create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/similarity_groups/.part-00000-ad5faba8-5922-42f1-a215-1619e7bb4e5d-c000.snappy.parquet.crc create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/similarity_groups/_SUCCESS create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/similarity_groups/part-00000-ad5faba8-5922-42f1-a215-1619e7bb4e5d-c000.snappy.parquet diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCollectSimRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCollectSimRels.java new file mode 100644 index 000000000..7c1e6550e --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCollectSimRels.java @@ -0,0 +1,167 @@ +package eu.dnetlib.dhp.oa.dedup; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.oaf.DataInfo; +import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import org.apache.commons.io.IOUtils; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaPairRDD; +import org.apache.spark.api.java.JavaRDD; +import org.apache.spark.sql.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import scala.Tuple2; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public class SparkCollectSimRels extends AbstractSparkAction { + + private static final Logger log = LoggerFactory.getLogger(SparkCollectSimRels.class); + + Dataset simGroupsDS; + Dataset groupsDS; + + public SparkCollectSimRels(ArgumentApplicationParser parser, SparkSession spark, Dataset simGroupsDS, Dataset groupsDS) { + super(parser, spark); + this.simGroupsDS = simGroupsDS; + this.groupsDS = groupsDS; + } + + public static void main(String[] args) throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkBlockStats.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/collectSimRels_parameters.json"))); + parser.parseArgument(args); + + SparkConf conf = new SparkConf(); + + final String dbUrl = parser.get("postgresUrl"); + final String dbUser = parser.get("postgresUser"); + final String dbPassword = parser.get("postgresPassword"); + + SparkSession spark = getSparkSession(conf); + + DataFrameReader readOptions = spark.read() + .format("jdbc") + .option("url", dbUrl) + .option("user", dbUser) + .option("password", dbPassword); + + new SparkCollectSimRels( + parser, + spark, + readOptions.option("dbtable", "similarity_groups").load(), + readOptions.option("dbtable", "groups").load() + ).run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); + } + + @Override + void run(ISLookUpService isLookUpService) { + + // read oozie parameters + final String isLookUpUrl = parser.get("isLookUpUrl"); + final String actionSetId = parser.get("actionSetId"); + final String workingPath = parser.get("workingPath"); + final int numPartitions = Optional + .ofNullable(parser.get("numPartitions")) + .map(Integer::valueOf) + .orElse(NUM_PARTITIONS); + final String dbUrl = parser.get("postgresUrl"); + final String dbUser = parser.get("postgresUser"); + + log.info("numPartitions: '{}'", numPartitions); + log.info("isLookUpUrl: '{}'", isLookUpUrl); + log.info("actionSetId: '{}'", actionSetId); + log.info("workingPath: '{}'", workingPath); + log.info("postgresUser: {}", dbUser); + log.info("postgresUrl: {}", dbUrl); + log.info("postgresPassword: xxx"); + + JavaPairRDD> similarityGroup = + simGroupsDS + .toJavaRDD() + .mapToPair(r -> new Tuple2<>(r.getString(0), r.getString(1))) + .groupByKey() + .mapToPair(i -> new Tuple2<>(i._1(), StreamSupport.stream(i._2().spliterator(), false) + .collect(Collectors.toList()))); + + JavaPairRDD groupIds = + groupsDS + .toJavaRDD() + .mapToPair(r -> new Tuple2<>(r.getString(0), r.getString(1))); + + JavaRDD, List>> groups = similarityGroup + .leftOuterJoin(groupIds) + .filter(g -> g._2()._2().isPresent()) + .map(g -> new Tuple2<>(new Tuple2<>(g._1(), g._2()._2().get()), g._2()._1())); + + JavaRDD relations = groups.flatMap(g -> { + String firstId = g._2().get(0); + List rels = new ArrayList<>(); + + for (String id : g._2()) { + if (!firstId.equals(id)) + rels.add(createSimRel(firstId, id, g._1()._2())); + } + + return rels.iterator(); + }); + + Dataset resultRelations = spark.createDataset( + relations.filter(r -> r.getRelType().equals("resultResult")).rdd(), + Encoders.bean(Relation.class) + ).repartition(numPartitions); + + Dataset organizationRelations = spark.createDataset( + relations.filter(r -> r.getRelType().equals("organizationOrganization")).rdd(), + Encoders.bean(Relation.class) + ).repartition(numPartitions); + + savePostgresRelation(organizationRelations, workingPath, actionSetId, "organization"); + savePostgresRelation(resultRelations, workingPath, actionSetId, "publication"); + savePostgresRelation(resultRelations, workingPath, actionSetId, "software"); + savePostgresRelation(resultRelations, workingPath, actionSetId, "otherresearchproduct"); + savePostgresRelation(resultRelations, workingPath, actionSetId, "dataset"); + + } + + private Relation createSimRel(String source, String target, String entity) { + final Relation r = new Relation(); + r.setSubRelType("dedupSimilarity"); + r.setRelClass("isSimilarTo"); + r.setDataInfo(new DataInfo()); + + switch (entity) { + case "result": + r.setSource("50|" + source); + r.setTarget("50|" + target); + r.setRelType("resultResult"); + break; + case "organization": + r.setSource("20|" + source); + r.setTarget("20|" + target); + r.setRelType("organizationOrganization"); + break; + default: + throw new IllegalArgumentException("unmanaged entity type: " + entity); + } + return r; + } + + private void savePostgresRelation(Dataset newRelations, String workingPath, String actionSetId, String entityType) { + newRelations + .write() + .mode(SaveMode.Append) + .parquet(DedupUtility.createSimRelPath(workingPath, actionSetId, entityType)); + } + +} \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateMergeRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateMergeRels.java index 6d625cd11..ce6226dde 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateMergeRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateMergeRels.java @@ -104,15 +104,13 @@ public class SparkCreateMergeRels extends AbstractSparkAction { .map(s -> MapDocumentUtil.getJPathString(dedupConf.getWf().getIdPath(), s)) .mapToPair((PairFunction) s -> new Tuple2<>(hash(s), s)); - final RDD> edgeRdd = spark - .read() - .textFile(DedupUtility.createSimRelPath(workingPath, actionSetId, subEntity)) - .map( - (MapFunction) r -> OBJECT_MAPPER.readValue(r, Relation.class), - Encoders.bean(Relation.class)) - .javaRDD() - .map(it -> new Edge<>(hash(it.getSource()), hash(it.getTarget()), it.getRelClass())) - .rdd(); + final RDD> edgeRdd = spark + .read() + .load(DedupUtility.createSimRelPath(workingPath, actionSetId, subEntity)) + .as(Encoders.bean(Relation.class)) + .javaRDD() + .map(it -> new Edge<>(hash(it.getSource()), hash(it.getTarget()), it.getRelClass())) + .rdd(); final Dataset mergeRels = spark .createDataset( diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateSimRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateSimRels.java index b3ee47bfc..babccefb4 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateSimRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateSimRels.java @@ -100,12 +100,17 @@ public class SparkCreateSimRels extends AbstractSparkAction { .repartition(numPartitions); // create relations by comparing only elements in the same group - Deduper - .computeRelations(sc, blocks, dedupConf) - .map(t -> createSimRel(t._1(), t._2(), entity)) - .repartition(numPartitions) - .map(r -> OBJECT_MAPPER.writeValueAsString(r)) - .saveAsTextFile(outputPath); + spark.createDataset( + Deduper + .computeRelations(sc, blocks, dedupConf) + .map(t -> createSimRel(t._1(), t._2(), entity)) + .repartition(numPartitions) + .rdd(), + Encoders.bean(Relation.class) + ) + .write() + .mode(SaveMode.Append) + .parquet(outputPath); } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/collectSimRels_parameters.json b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/collectSimRels_parameters.json new file mode 100644 index 000000000..da1011371 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/collectSimRels_parameters.json @@ -0,0 +1,44 @@ +[ + { + "paramName": "la", + "paramLongName": "isLookUpUrl", + "paramDescription": "address for the LookUp", + "paramRequired": true + }, + { + "paramName": "asi", + "paramLongName": "actionSetId", + "paramDescription": "action set identifier (name of the orchestrator)", + "paramRequired": true + }, + { + "paramName": "w", + "paramLongName": "workingPath", + "paramDescription": "path of the working directory", + "paramRequired": true + }, + { + "paramName": "np", + "paramLongName": "numPartitions", + "paramDescription": "number of partitions for the similarity relations intermediate phases", + "paramRequired": false + }, + { + "paramName": "purl", + "paramLongName": "postgresUrl", + "paramDescription": "the url of the postgres server", + "paramRequired": true + }, + { + "paramName": "pusr", + "paramLongName": "postgresUser", + "paramDescription": "the owner of the postgres database", + "paramRequired": true + }, + { + "paramName": "ppwd", + "paramLongName": "postgresPassword", + "paramDescription": "the password for the postgres user", + "paramRequired": true + } +] \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java index 35c4c7026..59c850591 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java @@ -1,4 +1,3 @@ - package eu.dnetlib.dhp.oa.dedup; import com.fasterxml.jackson.databind.ObjectMapper; @@ -16,10 +15,7 @@ import org.apache.spark.api.java.JavaSparkContext; import org.apache.spark.api.java.function.FilterFunction; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.api.java.function.PairFunction; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Encoders; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.SparkSession; +import org.apache.spark.sql.*; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -52,6 +48,7 @@ public class SparkDedupTest implements Serializable { private static String testOutputBasePath; private static String testDedupGraphBasePath; private static final String testActionSetId = "test-orchestrator"; + private static String testDedupAssertionsBasePath; @BeforeAll public static void cleanUp() throws IOException, URISyntaxException { @@ -66,6 +63,10 @@ public class SparkDedupTest implements Serializable { testDedupGraphBasePath = createTempDirectory(SparkDedupTest.class.getSimpleName() + "-") .toAbsolutePath() .toString(); + testDedupAssertionsBasePath = Paths + .get(SparkDedupTest.class.getResource("/eu/dnetlib/dhp/dedup/assertions").toURI()) + .toFile() + .getAbsolutePath(); FileUtils.deleteDirectory(new File(testOutputBasePath)); FileUtils.deleteDirectory(new File(testDedupGraphBasePath)); @@ -80,7 +81,8 @@ public class SparkDedupTest implements Serializable { .getOrCreate(); jsc = JavaSparkContext.fromSparkContext(spark.sparkContext()); - } + + } @BeforeEach public void setUp() throws IOException, ISLookUpException { @@ -150,6 +152,7 @@ public class SparkDedupTest implements Serializable { SparkCreateSimRels.class .getResourceAsStream( "/eu/dnetlib/dhp/oa/dedup/createSimRels_parameters.json"))); + parser .parseArgument( new String[] { @@ -162,30 +165,30 @@ public class SparkDedupTest implements Serializable { new SparkCreateSimRels(parser, spark).run(isLookUpService); - long orgs_simrel = spark - .read() - .textFile(testOutputBasePath + "/" + testActionSetId + "/organization_simrel") - .count(); + long orgs_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/organization_simrel") + .count(); - long pubs_simrel = spark - .read() - .textFile(testOutputBasePath + "/" + testActionSetId + "/publication_simrel") - .count(); + long pubs_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/publication_simrel") + .count(); - long sw_simrel = spark - .read() - .textFile(testOutputBasePath + "/" + testActionSetId + "/software_simrel") - .count(); + long sw_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/software_simrel") + .count(); - long ds_simrel = spark - .read() - .textFile(testOutputBasePath + "/" + testActionSetId + "/dataset_simrel") - .count(); + long ds_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/dataset_simrel") + .count(); - long orp_simrel = spark - .read() - .textFile(testOutputBasePath + "/" + testActionSetId + "/otherresearchproduct_simrel") - .count(); + long orp_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/otherresearchproduct_simrel") + .count(); assertEquals(3432, orgs_simrel); assertEquals(7152, pubs_simrel); @@ -194,8 +197,69 @@ public class SparkDedupTest implements Serializable { assertEquals(6750, orp_simrel); } + @Test + @Order(2) + public void collectSimRelsTest() throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCreateSimRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/collectSimRels_parameters.json"))); + parser + .parseArgument( + new String[] { + "-asi", testActionSetId, + "-la", "lookupurl", + "-w", testOutputBasePath, + "-np", "50", + "-purl", "jdbc:postgresql://localhost:5432/dnet_dedup", + "-pusr", "postgres_url", + "-ppwd", "" + }); + + new SparkCollectSimRels( + parser, + spark, + spark.read().load(testDedupAssertionsBasePath + "/similarity_groups"), + spark.read().load(testDedupAssertionsBasePath + "/groups") + ).run(null); + + long orgs_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/organization_simrel") + .count(); + + long pubs_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/publication_simrel") + .count(); + + long sw_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/software_simrel") + .count(); + + long ds_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/dataset_simrel") + .count(); + + long orp_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/otherresearchproduct_simrel") + .count(); + + assertEquals(4022, orgs_simrel); + assertEquals(10575, pubs_simrel); + assertEquals(3767, sw_simrel); + assertEquals(3881, ds_simrel); + assertEquals(10173, orp_simrel); + + } + @Test - @Order(2) + @Order(3) public void cutMergeRelsTest() throws Exception { ArgumentApplicationParser parser = new ArgumentApplicationParser( @@ -204,6 +268,7 @@ public class SparkDedupTest implements Serializable { SparkCreateMergeRels.class .getResourceAsStream( "/eu/dnetlib/dhp/oa/dedup/createCC_parameters.json"))); + parser .parseArgument( new String[] { @@ -290,7 +355,7 @@ public class SparkDedupTest implements Serializable { } @Test - @Order(3) + @Order(4) public void createMergeRelsTest() throws Exception { ArgumentApplicationParser parser = new ArgumentApplicationParser( @@ -299,6 +364,7 @@ public class SparkDedupTest implements Serializable { SparkCreateMergeRels.class .getResourceAsStream( "/eu/dnetlib/dhp/oa/dedup/createCC_parameters.json"))); + parser .parseArgument( new String[] { @@ -344,7 +410,7 @@ public class SparkDedupTest implements Serializable { } @Test - @Order(4) + @Order(5) public void createDedupRecordTest() throws Exception { ArgumentApplicationParser parser = new ArgumentApplicationParser( @@ -391,7 +457,7 @@ public class SparkDedupTest implements Serializable { } @Test - @Order(5) + @Order(6) public void updateEntityTest() throws Exception { ArgumentApplicationParser parser = new ArgumentApplicationParser( @@ -507,7 +573,7 @@ public class SparkDedupTest implements Serializable { } @Test - @Order(6) + @Order(7) public void propagateRelationTest() throws Exception { ArgumentApplicationParser parser = new ArgumentApplicationParser( @@ -557,7 +623,7 @@ public class SparkDedupTest implements Serializable { } @Test - @Order(7) + @Order(8) public void testRelations() throws Exception { testUniqueness("/eu/dnetlib/dhp/dedup/test/relation_1.json", 12, 10); testUniqueness("/eu/dnetlib/dhp/dedup/test/relation_2.json", 10, 2); @@ -575,11 +641,11 @@ public class SparkDedupTest implements Serializable { assertEquals(expected_unique, rel.distinct().count()); } - @AfterAll - public static void finalCleanUp() throws IOException { - FileUtils.deleteDirectory(new File(testOutputBasePath)); - FileUtils.deleteDirectory(new File(testDedupGraphBasePath)); - } +// @AfterAll +// public static void finalCleanUp() throws IOException { +// FileUtils.deleteDirectory(new File(testOutputBasePath)); +// FileUtils.deleteDirectory(new File(testDedupGraphBasePath)); +// } public boolean isDeletedByInference(String s) { return s.contains("\"deletedbyinference\":true"); diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/groups/._SUCCESS.crc b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/groups/._SUCCESS.crc new file mode 100644 index 0000000000000000000000000000000000000000..3b7b044936a890cd8d651d349a752d819d71d22c GIT binary patch literal 8 PcmYc;N@ieSU}69O2$TUk literal 0 HcmV?d00001 diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/groups/.part-00000-4bafcd13-3995-4d26-9cf4-eae22806175b-c000.snappy.parquet.crc b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/groups/.part-00000-4bafcd13-3995-4d26-9cf4-eae22806175b-c000.snappy.parquet.crc new file mode 100644 index 0000000000000000000000000000000000000000..de674144df42bfabdd39d794e21c52a765e06689 GIT binary patch literal 612 zcmV-q0-OC~a$^7h00ID`5)5()Yjk>!uHLO1F%$Wq|MnZOA-wde5|9&2e3U>R^GJAQ zw?fY>Q$B3m0bh)Viy)^31q8VdYhP*|YzT;!B`MO_j21`!l7WT`M4BLI$Zq(y^c9UD zd_pFX!IV>VTxg&c2qDvtcLT8?hY*x!L_!gly}1N}(R+={P~In?PTAxC^cSzGE-jPn=E~hvm)jxnUS%!tak7+fs1I$&BrfhHJ#)wWFc4rZV z)=4zY?J)>Jz=nLmEG-W3g@D5*yx(`01vmE8uKn;$E!J5gGnuaPy~_`+>ZCeUxPsmm zr@*@v&Os+rCvISobzYla(2jrAi6nEtun-YXE-i*p_-K#{fNU12_$`i#G=ccH{@O(? zvI~9fU?lK;4Q}wCKBSJyO%69;T9COt#nKTUUqyu0n$#aQcWedT6ZPJ3mo)?2+#@V` z=)R>J5-a-qL8!GHZ?{;8U@f4Y6HB)zba{@4p zu?%SebtCJ@q)fo6tykXp7HV=)g)8lq8?=c literal 0 HcmV?d00001 diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/groups/_SUCCESS b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/groups/_SUCCESS new file mode 100644 index 000000000..e69de29bb diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/groups/part-00000-4bafcd13-3995-4d26-9cf4-eae22806175b-c000.snappy.parquet b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/groups/part-00000-4bafcd13-3995-4d26-9cf4-eae22806175b-c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..ea7655139115567d5179623e49e2b5bb1d1933b2 GIT binary patch literal 77026 zcmZs^3v^s(b@o4#jFKby=+)79JRZwqd&V9)<5c3r!li1SCIp^K)-p_uXXTKR6ey+Kqu;M>Ha{h`Byyg6X6<_P(-!538)N8FgPvXe- zLOnDBOLx2^)Z-xW^enYPCr&dfjPqK{Fbva7_&YxjBHeNGfS6jc6x+YP>;f;e!rX~X zJ?w&TDJGM+q3bjQhaJ;?Z1J2!00PdwL& zO!;|+8>HNMs3(EL_hvkM6s4K&E|LZPW;f;y^xpaLCGI-FQmJFfBdE zBVK#trD+y~zG*G3Tav_%5tyl-CJ|v4xV9dK2Fsmzu^$;m7=)g@zsR>7*GzofG)(S3 zBWj2eo8@*5*UoLr@m=|Iwx8r-;BrI!I`4)53FK=JeJ_n{*LF?Ukw2FarFQOfSE)|` zx@oM3Hcu4fysjh)2#2Nhb)&@c?7-Etz~{Yqyzw9;61~JCeB&^4Q(JzXmwAMv=j(PH zvc9h8>Vd&~$xI_oi77jC!lmbMQ#Z}?Sa;nZ)SV>YJ^P-e`*!3;p6gqt|L1sdawE3f zP`3ZeT3E7w#%I z9Ug%Q;5icRAP!ScH{;l}%{a}&bZLFiA?gC8WaGjtqY(UXmzD zz0i&H#0iN%J5BX4Ak0E5GhNe;tTg=d`r3JJI9{$trY+--P-aKFy3e-+>;*rMmwt|W z^F!bD3G`Gxhe71sFl;3Ol-xJ&z&5O9r%M~HdH#9wCVu@|$?dF=@SYlrF5&$%Jd zZTXI#`h2KkUo~;`7+Q=KE(-%(>QiQolIbPxnm_(z7z-K zJ~LsZNW>xWV)?|g85(?^KFK`1{hpv;iLmg2zS7c+zMT*!CSiK6KRZ zL(_B(zQXf!ulR;hGAj9w;^7BYU}UVeU7TM0s7b9MxN<%8RGjkIXn3tWI9kmQSjAbx{tX5)2zHgjS zCcVIMS>asIy@2lwbA#0|T~eFtWMSr|j@dudcAd~qxjWwG!pSj;B^%6&bl`=ZyMgCh zsne`YrnW<};nvCQ#2Zyej{=u`Y`6ro!COfB=PKNwZyUvTl%k+HH|kLXvNWK^TPgcB zPBN!|YBCB9nN(cfn9Z9_*$L57X3ESUjqQM>*wLPp#W^5GbvvNcIix`zj$&pzly(pe z@4RD?d>NTWl*#Be16_PwDd|D;{cS7BLNoKdz{~sRnmw;~sTwkqvXQNTg$xX~mKi2w zvCNIFu(&u7MR^!eNO|w^urkV;M*+{scF!U}5|D(OvdkbLG1u3+!Mry@5;ptca4aaNl3o>>(!Ib-%5wM*`4z?pN%M9KxK%}_1iy$Tp2``t7 znHy9EFYVtq7Dk>AoTCVmWZ5DSb=PPPwQ`^i?=(%a{vpG*O^2PE>eL)}cvc3q%~ns% zz{�pePu{#mOYf>^zqE%a!@U01c8qY}U`neF`3UiqFc}9zG$ilQOLRhlfIc30DDS zB7%@TT^y?$83|7QNmOAk5=IM;4e*^=;Cdmi$*LB*Ci(j?kTu8fZ6eR-Imu$KZaG=x zW^odGNq=oJpkf9#$&vyhF$jqkD8>Kec3|ddWW-U&xDk^)l8_8Wnj_~H{{lpiQet@DWA6}}sY(8Ly zlK_tm(%@2k3R2TFETBvtxx?3udca24i$FJF>QJGQ%<34QGI*ZUPTBGa`_!SN>v0zF z_AHi=IvZGi-V(KClc8foz!}~1GQd%4@H%2XEuw1Lz7MMMlIFh2+|Kgc3U~oYPJsFe z*n={sXViPLzZ;mzrlCnPm62Gno}@|2)21Yd&?kXWWn<3_LdSKAW0fpQvMh{xD_PE~ zO@_}6CM<^^M}TwiG5=p|PmvM zy~~Rv1U0CdM5jl2PmHjm-S&VnX%Mr&(&lN!P0V2hWWw9YY%*bFXZ`yoSyBJ@ zFHYnE!Av(L!oj+x>r%gtjJNaDwL+EyWL6yC<9?}dxCjdXN(_$~3nckjKU+8yy_B~jE4#m%owZnii2pZS3 zSe#UM1u2C&rval-Q4w8GInTG94V+Rf!Kp^dy?b$1e1>mkzZ$7WFa_bJsR8gk{RvRT zbsT*vNO|{RVi{o`dWm6ojJ2c8b3L-Xo(2oMcahKuQudn!5ag}UcKkz=hOE7*K^>%R zi)OB>12GezSui|b$;r#1$@7zyOv5FHW8dU<(wr`iwIv2=MhqJuJHHoSPpLBq2GdI^gPVE94t#CC!yVH=Xud>?2Z#6ibcE%xarQmgsh zLKHPr+K*tSrxaT=_h}Yy-aa(h@Z5mi1Y(FDoEfiBkw{HqPfwl5q%>Q3e{H~HNBZ<0 ztdTLlrex&vbG$CT+?GkCjBsV!3Kd2AOIUti2ywmvD~sLfN^L=?ub!gBe1WV za~gG?m)$=Y5gP1Na%Y^7w0V4miahfN%>t!)^R>~OB4T77CB-+0mXyHVG}H=hdRH2i zG@swMD_1wH$CKozS7Z+Z~XKOG^sOu(%^WVB+^QGZae~D8bwDI~H$@LLaCr zTR}4=|fHNML+E%PpIh{Wi!eC!aAAQ`|DM>|<{J+UZJ)DJUFkDsn3lp7Gx z%p)Z~WM9*XSwvmvhF0K+`;>w`GTSofJIUIIVHMaD#3$J3Gmaje?ZT!Pl~nWFr!1nW%Q3uT1Tx?5CiRjQ-hUOZzd9-A}WI764kk`QMeYeBxBeaK!izSLOGEZV9(TJiskX26fQZ)+_XJ%1lNxb=s zT6_yEiU}mkMhOxtwCSwjXOf(XT_?9^peS2h)vJY8Y&QFWg_zw` zN-goY(lBF+gl(t6{cGEqQHmIo$Vw{NwNHKoQ)XN3Q&mbuuU_FqXtV7 zMPM42s0&;=9W(AYF$vtV@)T?nXE@C<5KntGIKI?dFDRHWKbFpYq4cleaR0N7`T_CBAy2FXJL4Tufu8dO&Z z6x3&~EB;Oi<6y)X_@9jG@VU|dA42uWi8j03hVmi%hs`N1pskH({I&_~Asu~-Ui zQBf=8(>RJ3-dBjkzGbD%6yI0tenfX`1aALSomvTTU^@#36%xF7rfZP;PCS6!D6$YR zadvhrhw-461EH3fr4*7Sw1>Z=S>R<`99OC*s||=x7R5Dm@le+oY#Tup!3fj8!^c8g z8D{rl+qGy;pz5N)ET8I}_yIjNa7dC?Y^E^kpQ4b9>7GI;Hg%S)ctKIvloLS>5axQ8 zA^y}ka+?RAAv)cXp6JmgMe)ySu6os@0j`BXbF2(&gi8+M884bcwe5~lDAIgipkT~EQc^p0NnaCsLo&pi=H zG3-U)L8uxu`2*}NnrTXo?L`Z;x2zc@S@$LNNe2Un966~{#I54d0k|Au-=VMMmj1F$ z?;BcddI*M&69cB5MpSZmtSBV|Z-==hb-`*x2^k_z<7Crmg|0??NKvUFj?g+Q>_}RM z`Fk61_3T1m$r*#yhZcsVhaoPG?aBe@feF0;>gS^j`d=#>9S9K%ph{q+oIp%>HNQ*$ z4TlbRkEn8P6n9Jy8V=MvG$eJB4#jb2zFaX$8DX;UxMur2X2)V1_QI58p>D;L#O**1 zGyzB&$WdY`N&RY}hdptVQjt_VVM4{CPvP0pNUT_1myCJ}mvlxMFkxXb2h1HtF}JiG zYL|#+H^ihqCyPDfuDt_fLIRAe>{@Ygx+akldP?+n^&}%;+%)N!Zow+UNRW|iah+On ztcjo$B@34}kS_unNHfdM()jojcc~6dC^6DXXgWN0o4MG{>JD^MxA%_Pc z4#`Cz00>err7un|3&41KM)zSrc(I6C<3#RJJm@}%H^c}BA%~`l@HNSk9vB%ubBW(2 zdw@|`xXdh`7zG%C7ir+b0Mdva-}9F>Y(-4TIETSBC+kX$eAZYUErciBT*}a%90QmUn+d50W z*|Eqz&TPmhT8cafnr~f*d1V)AjeuQhCQ?$%TAgf zDn!+GrA_=gJIakQb(_Ie#LK{Wd z&uqAS5~iJ5u%@ueP~E(2i{}cd>7rk$g(P|esA^$t+d#ZDrRHZ?;;gcZIt~LutCdEc zhX|6uyKkzs2+}l-*=R=j)bdi8i47IHP?QV{B!@U?K^uSoa!H`;zT4cVxoq)J_&qJs ziPRZQH{XAtM33=okNBWkaOY36=anis#o<$f$RvP)kJ*8RZ%L zbQi`l&5&1sqmVunDoQsSz(?1jrVyDCdx3S1V4&&QO|$>>5Fy3414xmp*uA+e-ljQd zj}UICme47NO9D^hz#LH;7M~~EN=TF8Lk(yEG(W@kJKC&P;u;f^r63?L?=k?O6iElI zA$7eV4}r@a?OH}tKsK8<#o4lAx!%MnjfRd`j1(`+t4goops!dEE`WQ}pII#Mliej- z7oX{=Kw2R~+!YM?aH6Oe@E}n_e>Td}$5av;SQ?@LXbuB?U;w5Qx|9F`h0okpaK-4# zl4*EzS4;d;*iwl?>MO; z3lEK{$Rne)J7LN$gC}5Bvy9SR9#twt3*4Cae7St6Dy%LTFElWh&FW_qezQ7S zE+v6@U3U%1hMv(CvsKD=972GC+t}<;`Cs~q+$Vg4VTgy6LbR~EMt&x5`{FrTgR}ux zz;-{QXb3f7ES4`DqD!Yz+K@6VM64Tj`TkWZ$~Kq_^_}L#qR&7GaYg@1NM3riVEEa3 zYLGdlB;9CdfD1F=<#;aTvE)W#aTV+-6ca2lnho^=H2~HgN>rR}DyYulg5|OmkR}3f zq|5pzD8cO6lx`$Lqvq9!Mt6x0^KMet%WL(NFH7LRs^@y^sO8+3;fqIWngl`yrPqL2 zG7^V+NkDp=_95P+#4%Qdgbq_2R7*$#9n0G+WgJ7ig4(M?%M988R6huZ9B?4UR%vu_ z)YmgB>PS9t5RUukzBU9lauuDa;S3+tcz-04^U5lY184Y^C4k=0G8MGriFb?WtWbL4 zISRgTds{&c0faDTBpr(yThnk@+(K3^tP-!T55nC_3xEZ72_eFq{?kSdNTNxBbfq1p z^K4N}q5Z&&{=zjXx0r33QoSf=YbXs+KCx?3O&a9^UL-|{cFC}k>h3BjAN>W=E)-W) zYED8Hjq~AW8@xH#d+3NACkNn2>DMt3(4>h2-6bm(U+tzuvgpA6#P-s#9CSvC7Fle8 z`RA%^lzib}ajXRs0omgL(MERH16D6e{CMJMA%z~^P92t#m4Qx2jwX*f$EPaPzR1gG z?q^d2t_;!cDrK(d?ri`J04{h1&M4wuQBQNpO|tkyeWMiqD966hLHKkR82WBZPFR3;ZbUy=F z0(wBEcSNB}Xlo!rC}HpiAUnf;bcmX8PATLI^95^uZWnkLqJv@tU4|HLf;s&|n)r6D z6htx@Ekrd(T-rZ~ZY)Ll%*6STu)4B)OZlh75fAin`Pqcyi?~Ivrwz?T>tl01UzB zT8>yDDk03V z9*CRQ0%gV9MaT55_$yr0NkD~10))` z3o#7(rjLkm8U)o@Rl}!}XJWikBOqbOuqG~+ZUR2w%Hd0FhO zjZzmNhUtKLXeiYTE6<41!aicyERAl>uTh-LE1rO0qPg)r)aE!fs_nIE49(e`QRsHW z#$Kw`;wt<+yhEZO<3~(*!65^-DQBzDu@Df#PzRyXbC%Zt{zFO|XGTz`G9K3UhX)t9eYDa zHUwX?1K6Z~UOlJ|kcNQ#lyY>Qg@bLIJNGkmE8fXjMX|Gp9zR=O2L@(o6310d)X*L9 z(Rkv~^=@OT;hNHOM}6IV6cQv+U^ydx$0!ofP;3BAM(g<*7KMRm=g%+0H zrs{03#vXUg=G#hUQa;-(ez_J9Dcq%4zea=Pb==OyHaR8dUfB_rn5@w{1fl}M!B5b9 zOCj&uyN2?{iqO!|n-E!<0ENZ0aJ$tyl%JacRI7VyVsp1dKX4mtkXniFIN6J%1nlhl zY;iCqRN^Gc3}QXXT#R5uFGebjOu8Pv4S}9*7x`z5$&B!)f1-V&WJsF@&Anthp!AWz zu~&HR5tMSYRXal4Pa?B9B+gwduIz22gTmjzdRXZL<7x&$B<`T8jYuP-A=))94kZ-n zHN~4DvuqtdL&~1X#A}zcKNfq$VwXbADIYB>Zs>@w^frguMvNj2C$5{07+O|xJn`7- zl97skSX03!L>}*JbZe1sK%A^z0!lB`3jX{)$jdi$mGV%GEf=$mBV&Vj{1A;mnI5(b z+7NMUB`Q6L13Lh4&x8fZi{q^rzzGr|DTr@tVtbd$ZJR=0Rrg|`Pa2Z8LQ z)#Ag;6>N7RST1c}aNsmg87#@jY#|pHY@~;z^YUqpOcAUXPj6_E!YTD=fyNG_2H1k_ zx5V|G&zEjLpJhgAl*ShfZ#hPv*zFiAxwg2XC5*;iL_ZdUbc~D4fg~Y``U!?7zyEX_ zuMQd&lysUEkMJ(PyjC2kv>|%1W?>n0AQHuOts0Gkk1J4&uPQn2JRKC(rd4-hng!VU z?ASo&0RdPY+Av?NSkLFwvEa}r=azV=x0EKd*n@CHcoHdfu>5Sdkg8qJN!=iifTURR zv+Chyi%>DKio!04%a@Ny`y@IT-7UFQ~U-Hl0>^R;qfXA?$VzBIIP8?BsNkYK`zAkGGO$#8STI~WeqdI*9u z!sW%+LD1QV?|p?zTha|Qy0B-7I8axmu+jX}0B}1d2|&YV;h~_7HH>XeX0R>Ms4HN1 z6rL6V1FMRaH4@*h4jYm`iNW@0!0t?86oVRV4W1{EJT0$H8(cj`)pJdh1?YDE*VGT4 zf_#Q-Par5!I}<0n>y#F-`)%<@igUTJwuaS%9y=F@8g;}#69;Zqo$6C51gUs)e?b?g zR#zK_5~YdwYLkwZZqE}NRu-q*kjPYVjAiVj)5~@xtQ}ewNHg}yBXJ%4v-#{YWXTPRmxhQ2$)+l+&#P14zd1~2_f;e@NiZ3LZUsZwX0B!cyYOss} zSWBGPP)L`0%)@m9Is>yN`bD03F~x`y1nCYVO$UdFuzPhOoGH*}o3xME@lc6_BVz`4 zVnMIaR`4Ly2%;zwYg(vTG(ds)L^q&@53#W8fiV;IlGxo(xHjlXvNTWaOzgRsaMeQU zTr_+~$#*duM3D87q+{@z&_hy>(zIh7CDo;7iyv+*EiM5xM}}T08)ri)8P+X8cXV5XDd!nOIT6>>E88- z1F)K4{lF0WTHKWAR!JdqAX9O1y#)hsGojDuLvj`kz7>IhcOd?` zp;D_X@tak{yIU^cEW|vk8^iAtPplXur&DinokAjmedEsQ2Anx|TUZ+LOj#UVQ}DzG zs{@i4jAOiUkh2erjmHq8NKFC4J(deR@RXATPMWu8;ByqeH9N*tq!?@Dd|IrVFH*_& zff!m*@^o=Ur4rB^Wgw*G^*AO$xJfwiC`ELXj2@XJ#}v~(vYrpuRNl9&o`pC$JR=cz z^cKST2g!M1Kx^YAWGmuGtld;xL`^62IjJx;F;)?u--xja+5?9g`N~clvr1ErOCg47 z1V%xPigsH}Ei>q$Kn!7u4ay{it`%SGRAA%gYtd+lL~?3PQEwroLyIMLj7P9U2lw2S zsFf82de|d`4;dBR5P3(7}D~?Gw0fP`{9sD zcfg0q0R?K3-e`dS1_A=l3My2!(LHEm&4}pO=|OFjAFn*Us)do3o*$7b5HD<$wa$s> zDcFHoDRH=t%F-g&WAQ*Q&cqwl>amUZmGHSDdC8o2tho#JnDzpD4`#j)7YJQEb`1@K zp=pR_e+l(!dQ~a#=|H9Z*@%5cHpsN$qcvHBT=85@gAh#O;d9{7FKA2`wCP343w!mzk^MG07N0&_wHGHlQQr+i5kl_xvf;8Da~U<^HEp#mvz z@ka4%FX8+7PT4m=(9r(bbfMgrI69!h3^)s$13d6J(%I@JQv<$Nom*EyB1J_I545Q3 zhr0{G#P3zKX}$csb%kjDv(*57Uz! zA=eCsCkT9;C>2dQ_#<(lBED6tfd(j7uxtG8A3cXhJs=u#N1VOdja;^=_M9plf%Ge_q%v`*n$#Vc2^lw{1KwfTMG?$s^&9I6h4 zyk8yKGKt}q@`&pcs*fB@I)$DgD*$21O{m*E`C+A%28c^|`t(G6amy~`Ps%iE5xzXI z7rqzUeCi|=U?M&(#6xvaRdzYBfXwA!T!VSSJBy{s?B7>NCw?TZ*jREv%*!=MN|UcI!xofd(KvF_k;2%N`??oy0HdFcT=AM&7X~?${#M zt{1&Z$r6 zqB|``ae1*z1v>}jm(*IGjk5w2?}Z&FG?Yr;n|VjcGOLT$5T5T0aDn-Vr9(_HJ5K>HKUZFF;{NiZ3iabJ6F8;HWMNF#MBb4Mt)ZC)D7fRiI) zQ?g9e=oO2G1VIXlwP;uP+{v1l-cZ4%ms6gfUQZGtdtIm~co4*oyB1F3n%V=QN|A+6 zVsZt0|Cy5nB={8ZnT92Fg*LFAw1}!gxMGkXQ8D~b-b*@z3P_~bY`V=Ca!1KJm7y

NLRt`0E$D)X9vUj6h9?XX*y=mF9B4t|fhSAK2h~w% zdO2=H)+4xb-{n2+AFZR0fGx3l-O|TbiuS8&UJTkapKBPhET`m1nSss$EdFEt=fj$@*F@JG^#aUBAc~Df9pUlgjTdT4Wz3iB{e+9xL zc+#_Gn9(~vrNNwdrug?uGz36DIizU@b1=^Q+e%p^jzKN}0fXW1lwjQVgcbDfAj3<| zJ(Cx~%RDZ;%_xl=OCg7-(EKed&3^dkR2!!dB^zT0IOy0)@zssu8ynlw^T(&)DT(Ph z5DuYD#DBsjCtxK)=uK^`Y;+YUCQviL&8p$3BymIBX_s{W&|XZu{57+15O|>fRD5|^ zXIrBuhlra*854GmAap}XMaJ{*S3$Ue_}7b&V-VkcjC4nr)3$1z;^ZY_L+5}LqnQbz zuwtb0S<&7wqEw;u5+uj6GUi#dH#n7%J+uSylQ0U5AzO`BmD}fFH-4 zL{$yb>VakjTZP9P`}8`v%M$5qaV=aTFB%!oWDFDP=J;uL!#8w#X-31$R2EDiBb2&P zRZ%W;@%$E%(9vd5QF#35iV~)b+Pa1mMjSpW7e~t#EO#WRa-+L$W}s=tNX4+FQO9ZQ z4{t8P0vZmqhmA)*1EgVhhM{)F!HaoECC4s*v`fwi`RsgTFw%?_4J(id4zwWzIZVJ! zYfB`XgRA9yE9wHJ6JvlG6WGkc7^!siK!y%2YkqftF)aL?P-y|Z;)VU$;kJ!1gW;RK z=N~5tS;g@Rt4f6tkFPE`hxWEZ`amq&R4EU3v*4BY4j?qDal~eMFAY?3=#*+h1c+PI zl5Q?sr^;zWF|M~@fykSV$z#60T|Ch?sT=s};8+YABgLV89#yf)=<+ZQM<7BBdwgU0MO|ngc!Qa{3c~6M zolV4$lDI{aelf%k9!Q+tG5|HfgrSM-hgVkX3>bPO!%HI8X8gkuZdF4Y#iDk)UPDyD za~9zRx3T;HKWUPn=Yvkxt${U!`~_n#UPg0S9c?yp#)2S)X|!rRV}u*@59~Jt1RERN znA_y*F1#5~UE)74qTio5DlTrq%PuKyxr+(*n8;nMOPTOIH6i{kDO5)1?It-wk zw{*pPxjDCsR}Zd{gMff3Ts>gIQ0RJ#<`6EK>Aqmc!8QyGQ7wDsv+CHYiigdMOQ=8yEY6Kc`yvvq>1GMd|VZbW#*i)hepVqZ`SJ;ZoX z-l)K~RF8GDQ4;zG!ieaG{}Yp@G*nztugm!v90XZkU{=s1m;sftiXVx+ChrSNCJ&H^ zo@NE+5~le0PzAC$W6REl;-#+ScE=#2*0*W*BW6>9{PF&?3N8`x&QL)co}bci17PMk zuSLd3HAvXD)!BwdHiG^?*`Q59UMpdz`>0Nh|Eo$JNe=T#mQ=A5AcWzZ!x}Z(l(MkB z0(Tl)amAWC15nh)(0`y^vQzQo7KIWce$m;2Czc*T{Hokm4I~GDVghJbrAOD&4#8Co zKUN_%NFeTp&_!nP#j20ad`N3Y>=7fz02W8$bRf=E=~ZJ_bad6}U?>u4f;SjeAr*J8 zfr;cPpY9U(wgz##!5M;`u-YJ$IN9MAE=iC&kDkMRi*0TAse?)#S%R&JZ|~=ajzS;o zsFmns&O-i+BW?JNkM^_~r-NQc2#TGNgLR9+IczfAdrdS}mS`6bm*J_`*eDP0ZHU)+ z!?P?KSaQEgoGx!OBbn`Rm;k18;>H?3h)debo|A9-hHTR0V9oTUL-yt7UiY*Qewsqf5*VTb?^m{r<@P<*qCl||L*9E7}J zMWN8)CTNCC(d-pBUeuhbFn7Z=xp>8DaP_ELBj|A{xMJrP77C&nA5pcotOlvZ1Z*Np zxfvZJsQ4uMa0CnSkgdQlKQK)#H?Az0EEt&`AbI0A z|$a(ZEQI<+F9o zV(!`zxgvpDZBvCaC)h|6?~&(Gpa{s5BFaNrn?0VhvE<+idZPlGrib9RQaR#}GZ+S9 zG#DkfSw$SM%Bd$ zn_z6(w?cz|4oGwn^x{W1v4~SkwI-IrQmt~d123+x2MSTC#=xm$(4`~P|k)_z@q`4o=pyxANuy^>rRo&gIfF4A+ zUd4ZD78k{Tk-Jb@s|_Z2F}z}OtnSmqTP((ZAffz@u|kaUzb~jUCk?}qiWing&S@rz zG;Z!IU|?LsCfjqBf{B4#TZ^%gl0&Nd*46&|z!uTlP5Bf*ZDC9h&#Z0yf5)}p_its` zLUBC82S-n0+D2b2+1H;`vt;tR1uX$*GTt*xlfZrus(lQ<65I_<(vI{W5(0 zqo-A%bv(b3f^wN9YAq>-C2o0qgZ!AM^%5@6A(c9pi0`x&_{aGnzGS-6NSU&gR>?q& zY%W>$ncW)wFfz{jwn@Xs>1B-Nml6eVhlS9Zxb(U-8W;5#%SWx7WxQM z1p*@)9Xk$B9Fy$TliM^4vyvZuo}eyHSCAV3AjKCoFjrs&MG~wYYm#Z1l5iQ;d7_hM zWU8e=+la1JBHvO19{)|BG~aEElUOKMT7nOd-pjsbjFg^e?l}u-YIZ zafza$nRy~wxf^Q*^foAlG>s^W`w-V1e%ZqfSSheKt{m~UE=AH`V`y@OzC5mkx!(*u z>(FG4q)APPX{Shuqve9dZei*X&5c&bjwh6n-8JY2I2a!yaroeNh;O_(pmnkk4oO75 zYP(JMjRLy3jOXvUV%QLkD+_M%PiUL;{fx3!we_NLQH@+C?dW1^od!3r$K`(>MmXZ! z37oG;=5*-T7{$ldl$b6U_bVvnJXd+0XtBfK`w;1j;S0A#;@kF77H1x_?f$DpNdT?LjWl8KvxaJ3HQ&HP$ItHsPI!5Q~HM1wUKFZ46JxI zlZjK}XwqMlh}EAjhaMzoL99q8fXY#(~o_*e>72%Hku#<7Zr@=&ovrjN4CV* zU9B>{>5926;_!CTWp}TF7`nys7P`gpQskAFZ-H}o!6o2GWHcHL z!TcT=9S zazfxKQVvHtRF7TSV#tiyeV_=AoGVJ~^G^r7B)7r4&cqX-q26JmWI5uZPL)vxbLPv? zxQ^MpuK@#sA(F=t$EzSQMD`p>kuqwDGn?@C;#Kj*%UUX>B@urBepU}Cs$r$RDC!y# z0XS}~N9Ct*jj1NYfZKFZ$|I{PP=As)K6ph7XoQ)7Nr9|-u))L| zU7&by9WqHKEp$H^l8!Q~tA_6=!j4zO#L;Hj0LK?2`7l(|kcXjo^GNgFRsbOn4GJPa zU>^WS$Rnu0U^%RMOWb!kWOQ^%4J_?(yii|bV;i{#c^H(=Pf%6LtJ{1zbF~ja0-gX=kv{ZN2;lF5HP=Qu089xTap-TW2 z9A5#EAMyNfNJcdLJ~@*&hWNlx2h#+Ic@ev3Ki&H0umH}*;6ZWc`w!EoN^)iVDw?g~gLE6` z`Z;-k0dA^U8JR)je7)gWIm*U?KlS;_Cw?A6%!>0TVzgTk_1Gm_uNKgdu7871SE> ziUwP!-BYlz$=hVJU+kzcY=LAI^zE(HIUN84930DcyC8lTf(p~(9tCb@_)Fw==p)k4 z>&d8|*fdh|a&c}{9N5@Ef^_l15?^m#RXy9yL2P5wWL z(?bEqGh6K0EbiIJ#jC_n54kgEa2k&yM@XO{Fztr^fNtlC+qE|K zQ93NbB5~nQ#4oQXL?h>yZ|Y%9wNBu9bcrU9_6S6OcgNTOO1+%fL?0o7PF`Db5;4A3 z+>66q*w{3WyK#3*BYTycWzA zZcdZ~yvHtMi{jNAJH}XB7<>zU;3$4ydCcVv3>87sV-L-0&+J z%y8jo=YwF++26l&R78xd8i+Qdq!<98_WHH_P2BtYcP=+OX zv4w@qWXyu{RX!M2Vk~b#KGE0UpcAMPs>lAr6{s$R--X>$wJrEHj$2{&JQW8nt;0_E zDM~0x9kK#!gV@+FiGFR(s4WjXkQ1>CR_6493~5x4)-MpmJsAGK%52EIU?XAdF7P6YXIID0pPL1ZX&OH50LL^ceVq2MQJZ z&YbK~d_?@vtt6o+Ej3cXE|Czr7iUF zL~B$U7{`aY?>{6PM+Fj3EH#JR(sfcP5Acv~w!PJiEMh6=m>!u!s;D(@f3-#+kU|ATS*8LB}+} z8&_~BUb(6~yG_H$&3gXBdQLPGe}9qqYop-y4-Kc-mzheId8oZX{9cG_4GDEm>ly-O zm@*h$XJi0rj{zuTnYsffYo8qOA)m9;%eO2xowLs6XC!tNN^ zLS>{cv%GXU6DISb+dWC9J5lQc`TeqA@H5c#oq564v)!KR@)1}w5t=vJ@mF9&Pz zE8^7w7(nkJ#k-8P!F~C;BzJG>6Q|4}S{~-Z>GS~xyu-ljTO|2PffH?^e9sGjNtiRj zGIQ;~0-z3}@dJfcO8-b-i!U4)h(cJj6wr@2uUmZYa$>EcXZU0ddkDUEueiTit8>r_ zs0Nq8zgilsrs0VfgvGWLzA#;LJdVq86hj^d?cj}(16DjJwqfz>v0__eG@q^d*{cd6 z{Xqq78bgFQekpY6zY#llM5|g&vMgU)E2^jRv^^ zEaA_o!&j2I!`V_D!4j%bvI#LH_XGUL;|rNDp# zQc$);_st4$q00{#E1caUaj$tVWapml~C z-MOuLwyt8GPQ`tf;z%h^%2wuNgP6xO6`j;zq%-%fqa`zLzK_o7ts0z6u`O;9zeURS(QoeZ6 zHKjzKSS3-)P#@Nl=*+_i+fc-ITHQMUTg0FT=^Z5hWV1y5{lImk#Zk8r_XGQ_`BIm3X1{X}_vk(m*B2B@5jhQ&Vhoa|RiCj!#sVwhu; z!bIj32ZZ?fmXXa_%$NVFrGU@M z2bb*bM{mUVQ}B+z9gOW)CwFp63a4Sld^;#Zq#hRdJq zsc_~H$D@8~Tw_9niaKNuAKXPx2S|mlVk(@mL3~w#0SiL1hiYHt%PFFLTS`>d*=tpd zIY676a9Y!ISC8(j!MAXtg3mmGIDPdfL(!A~Oy^8$VUFPiSlF;CoK!=77eAEXV01IN zc9P)+q!11&;S>N|#DF$! z6dzt+!P*OrEnadFr|j7{KJZhxh?@ggH#ocrWQIap#-h6OX5<4}2#C2qUZo&5?SOoD z(2EwXE11w7)W07LXjqFOeQvC(%!^@1=!a0TQ8AhcTVwQ64i`WqL;-Ufpk2LAeuedf@63@J zy$iBrJn@q{z>?$=nNJzwx4pHcQ*5EDL60U}L7TxhOf=|@c{cOKL#I%jYS4G!S*YjA z5d93w9>D~y4E-GB;;Ay6;I2U+3HD6#h&Z@S9C*RR))r?DfK=)C;%eKXc)n-@g0lqg zD2=5goS)+-6*q9j1SqO%tggtzz}SF4taBr?DncVi>0yJ1Wn`8EijM)=-5O3ij0_KG z(w#der)p?tvf+p8^jyesoG&uoUh+XD+lr4w2NYs{_%V(CjlKG`sxbTV=$uBK!eQ`8 zmzYDPFORQPV#^shRO1|I>{U#tV{OW;@@={zjXZ6?lxC8$z!QS}1joJbnx_t`s57p3 z$VDU?ze#LeE~V)Ylp#v-RDWw@A;p%fGV&dkAF(K7!0GX;7{%n!6L|!gC#X<#Oc>9Jb9Lp(8?737pYzyM2n_3+BhX_hb4-w;(5QMLK4$u%6r8v-Ru2)C~<%gd~ zjd;!;D6s0}niga$q9it8=Ah3=Q)w7jxk+GuH(nVajc;Z!oS@?3J&+^;P^A8FsB0Tl#M^l>YqRX1L(be^FEa z=PxY%R~9zD>%adS3&jqt_?cqi{Do@q{6E%T+ zRoJ<)u;uDPP5$12TQhEU&x#lR&zoKUzdrVVeMoK1|N5E#`PEBLrXT(9Cp)jBP%IYY zvpsM#4|nrTJl%O0-gM-{d+xniKHz!s0ng(Br~Y!qmxZ~VwZy=wi|;IgM`g~AO*qrUgil5=69u&1c1w{Lm; zYJR5vxPJpb^P^Aw$F>T`?iXA2J;4?K#gE@rRO|8nuWr1yP?+K$>$iV*YPVTCv+u?^v)kIO?Zp7pOZL<1dE9>mTRxs>P2#!R6mSBmSMscg)wF=5ptg&X2fkJkONxb z^M$RA?pxJ;-P3Z%@6FfA_#gVls%b8*t(P6+Yo343UdyH*=cOI~_|^T*LgBC2^zYpo zzMl*(Q^C=l@0cLV+{3$CKYL<*XSb@J_u$hqk~c2@qR*u~HER?ymfv`+IH=D3Q{@-@$>#>wf0>2<2A{G1gV$X{Abyv}`q6c_-FQu*aF&0( z^}?|~)C+~*6xE=9>f!6p<3GxAkRW->dsofBw}aRpKeCtK|My}|dDlCyUN~6zZBgCz z;Ir{g-b!}vFUPrltaSn+-?pZn2wzAAsC@g^cc%fI=G?rQg-s(kGh8F;te{Vo|) zjddTtmCL*KjE!@7@*}tZ6_?k46T`C$80C%|94gx3c?d>W_QhA+z$E<~F!o zo_vWWAL`bH7t7Cn=C50ySmRIUMsAS%X8jkA9^~@1`5!;P<$cz& zZ*kc>e@^DtkBZe#b2;$AOXR+*f93yi_OEePmt`C9KjeT5SaY!$)(mT4fEgTyVH|Nb zYt05nozW2yLD7&v(Z!n0k_^oZ4a*&gMtdtPMN@EZSvM7$c54ZVp&6RFPmNJj^tPf> zOSAfaFM3}5Up%j#?$77Ga2RIR@A_TWxjD|`JkEnhRyo%v)IU<@tHtyQ(QKUe<*O!I z_WkVD=ngL0lRVt=^0cidTjuVmIL)&Az>2sFmu=m(|2R(gWZdb@DRcJQ(sysV%75*B z>0eh{(pz_b-?HSYH-2cDddJs)VrjVki2t=bbJVT}EW6t;-eK8$`!&C{eD>6fp0V8D zetF!e>$|ta>-gTN?mt;NUp?wCmWJZfJuieN;drZw*5vgd44Uo zu=MsV+`=^_b+fpIRbLM+{z0;PXV%i4d9v4X?gt-<*X`#E7eos<>AFvxWVvhMiAyZg zlV3R9(tYub>|yej-VrM;+oyy}Eu&sJ|2oTAo3`9#x%Y4H|Jbtik59%YdUWYS@suSF zz2!;E-t2E*vb3+C6rUlv@tQZ{-);C5*Oolz=_BKZKjHVQ5|a<Z?PP;{eyT8ySsYgp06MM-s6_~w(93BC0o80Kib26 zH@|N=;>vSA^a(Hd!|(yshoqBdycWj!>Y61p$6HoD|3IbX7j+H6VuUtgzX>9*QvzW46?)X|p5KHPDP<(uDl;RMT`6EkO7_C0*+*_Ju!i!ZYD zU;2rQEq#fm7+tI@-1ue7_m2DED$8w0|L$5#@`}yz$to{g6c5$B=l*z)rTw{m4_dDL z;cvEEZn*yBM=W2t{*-9iIv+gg1#0CHS_W@H5 z`AV|;stNHf?ET5Oc$!|gudBlH@2MlES(?^-CO+Ao@_Rf|D*MGF->WHGoVw(h!pK_I zeD$%HO?LA7kG(uxKl-&T$$Q7{?QrXE(s}%WAGKtU*?pR&sq33@bGI(vu-;NUC$lMj@qk03ZCJH@(-zCA z%WwXfWp84|PRrEa{pB^kV(YZlWe1hrk$ijA$IkQBtJaN-?_uusTjC4Y^6IVMwalLI ze*E`8-0_EBSng^6XY?j}uf8UJ(*9pOw2#whs`}TN+`|vTn$gKq&-?U+{$};OW$|a+ zQa2D^;=`l9v(<9R=BIyd`Rj`pMiboplV#6Xo_k{U>y`z{-~Gih_wvkte6T%t9dY1- zvURBkyDpx&(6+C5z5QT+d&$i4D=dx2mz`wtJcR-Uvm9-R07wPd>Gxhp5mvh4bM z?nq0vt2El0-P?Y1uI0zSsf!2whWQ)fudlf*eB1KvBj(1PZCXAxUem3Y)6^FJ5F z13Rhy{`m64h3VW!IEeX=t%wKfmET_L*%EIZb$7U<{EC|7p8G!dhGREo)jt0;@y^g= z=yN0MlJgr|{^85JUO9s0PcELiJMQJ0%WofjK-rq)@~{7MtgqJHesjul%`3-NThhTZb zJ>FaS?h}^eMfbm8dF88fUXFj4eRQ|wya&D%E$V}*r~YVpVeQ*-lYW?b_V1Pp8ZM1{ z)iCFPbm<|NC3ip7JJnaqvwx_!eDA6G-ImK<-x=S^O*=naX!+SaYZqG{`d;&L%a)m& zqG_7+$*a$?Tyw+TwU%#BJ|#ZVCExsRd`FMH_WI4154Y{xVmbF;8}7F>-SEK!maCTM z9=H3a{{38DXvtrWd(PH39kBEF{_2eWp?H=i9q?<{EZO>(6W_MnbLZ*tWWAhT5F>&9 z-z;O9QgzFZ9Xpb9c=999?l(zi@67ni%={;3STeZuv<2)_zNS<~3W9eC4p=vn_+4I&F=m z?&7<@W=VW;^fxTqZ~J|`iI*JPdyVCsN1u%^d&LtYZnR|cZ{KG5(?vhO%kqzN&t|(4 zHNCU!s}K5-N8fzjFRnlIM>b>9*3l(@3WJI2lCzF|R}S@#0Cv%sU$n}h689Yc)wN9{ zGu8hX_2sYBl$3lhLhiF;`?uK34`#l)drVBYGRZk>m+bXRN-96W`lU`i=Aq13Inq7f ze07Siwtw$C^L=&H-hZD`=BsDj)^(<@zVQ2dK4&>$;%V`nCUS>-)v|TztPSIn3!a>I zMO9gM;!Edc!dFrw z=Oy=yI+EE*{B`J+kVtvr?q@&#f0BUjk64x3K4*N08De6=?FG5qV;;HbN9TOPNB!B$ zC#8JT1e!P{HJgWDw?&7_1h)R{KWC3DDLKU-fB63LgKX1H{@A^1-83KaR-f#)&mPNXHTGc%<4GePtG@T|kE~v}KgX(G@%Uf=S9*BPh~%WNR5BBx zXUYfTGpUcbE8d<%@n7 zo*FZ9amhWEFHW&t_y6pfs)$^jdfcPYSnv6B-ghPc^lCW6^43dl#M^xTn@4=wl5Xi> zrBkmQde*@Q_!lL~&m85e=k~r6f9?qneU;MX$aTqG7py*ie5T={v)exK^tbw?u*rMN zDz=yX|D%)1l6e0emLJMYGs%(|l288cf9;1IUmW$$X$N@TIJb7pr6r{`50;dsS3Ec( zQMc*Ak9dO*9+2v**;blfykgtP%;2VNqpDZ$-gaQkx|;2Yx(zF~k8ZeS)Alhg!W0ep%MJXVWi3_dC0PIj*O)_Muc?y718Wd3Bo~DqoNterUp?zS#fu9& zDwYmz-ch-H_3(~~E7sLMoEh9uc(`iiEt?;nv})_{!;{Z_sPqG9{bJMS9-~Y}lzdrQA(t{pr zekeWgSj*13n;&a^Ec@zXZBO(a^qcHciwAy_dv5UN-?YED`qkfb>|S@!uFgFh26lDr zz2)XzbKc(i>aN4~J#^6H-S0d#@c7~X*mLvaM|}9stB)U9GP3T8p3;iJCwdbzZh7LU zWNwd6ieu`2+m}9N@VCcghHm-o-0Ji9{Px(IE9#z{S9kT`lgBmOddrjZo9^E8VCH%`}E-N7IwaN%kNI;es|CBPV5;uYXvXS-Atyou4e6Fy5#%<5_e=1Ew`1lXE_&zbGyib$A^&*o_CH+m;k$4A;nI>( z4ZANZtz5Z#ZKD2;-Jegk@7;ZQYHq{uy7ZEj!&hXAcMM-yeZk)0FVtMw@XGqSZ?Ami ziw(Ek@yeH)?%Dgwmzy7Mc=apUXI8%Y)y~)Nc=c=D@9ll{>pi1p?YXM2^5c8HF|Yp4 zJ>Oi={^p)(>l^`{S=)yYjX>U*EXu zo;P2=?%aoGy>b2OXFmSMch|gr=NmU%^xm6q+<4ij#=V=?Ri3qX^ZNSl?Y-$M?QiY9 z`Kq~%Z{D(D$ysmSdQI_rZ{BwO1#i81`=%=!-@4wB$o<#y|Cv5L zclFTO=jQ2&Xwi&)pK+ukafDVa81#qC9||6>Z~vIzS>qRKlejjk|i;uwf{d)mTuSeitwPpzepHynu&IdKS_978e; zNsc;3CR5_C9qKO`!yIK9XV#B^3Z6%~lHQCxUyW@HF@zGE*Zi=vKu`91nHL42WH!4Pwf6z@4 z2TJI?rIt)BqauD1TSS@3TsVvev=V=cqy{Z18WWUm^LpxWQhb>TpRSHLLkoiyTY_>u z#l8JM1Vxx0UV(-Mk+0ZjObC-)xw(c|4I1I|8pHMz9CVLTVxr}!*5`4w%#f?{>*6O- zh17v+=p(YM@eugQn(Rn2w4u)`{tx6aUX4D`7O6wgJTCN4gWQyx+h{jo01U<-q9vK~ zG~p~-`uJO<>K&k>Zp=6b4ka&-Q|A28vNec>c%oWilrG76la$tVef;;6=*x|wgDU(= zsmAfPV`Q1r<}}8E4tY|i_+S_V8t~~j;6-=4Vtx+=T6DqvLR_t+@QsxF z=@#Q~qXapsYc8)XM$y~p#oq3xSwts^1QVt_t!FZFg}Enf^fbD`@y?uZEuN;##t}sH zU9}8R2E>WU6YO)y47BSYe1P(**w~K5ikDkZ7S9eo{X&~p^!AIGS z;^=wmRQL*XxT%AnP9V5BvF#fB@kinjuGpZ_+e;zBVhSNaM(QgA*e|sC;#}PIEycW@ zGkTb$BNSy(fK~{T1`kb!tDo7x&Fx`-isDyd87Wu;gfZ7Mt@1R_Ojf z`YM|7Vyc@NQU+$2QDAfFk+oFJe-7EoR%$OW_z7d5_Mu~1^nqz!_S2%y?;kjZ_3Ww4 z+lzC^3|K9$olaf=+qrH3`5;*8(N&-wbT1ln$ANViTwa}5ZF!0hwnKNK+v+iW4|%H& zCk&kjwK2EJQeP@5Gx}(K`2}MJy4i z6rJo;L_5&`G;|x8=@)B`WDzv>v5JIKF)e9?eq`u_fZVu0L;Pt=FC)1T57Sc zh0=i5GV(HN`N*H1U3YMvsr0s@$YPWXwHHN-)YSUv)(&CrWGm*Uv{7o(p7NaTTM9NP z$3?>f*#xXkbkEkj#`{5yC6QIbT*q$GHf^M4^f~#&(h{(#K9a40$C0;I{IC> z%epC<{aoIZ`EpB%U-(+f;Mth%l>B}6v0w-vdTz9wD>$*?3vW@WJpVY zYkp8uifa@bpmbx9YvV$gpE$*p`OOtskWS}V8yVk0bi^suOH zR`i0{4Z;vuIZ=qIfB##Fb8I}kQNWy$D@VT{HIhPZ95SQbCy$m)0mUHMzO~uD?ZCFpN#bTUnMy*TlMhlz! zU;m)H&U=lUMHoV7ym4eArM*uc~&_}eXaRMWe2KR+E^1s;O96w11({E%j zn{Q->ljDYB4jX<&Yn(f7&1>V1b88%7;wvVvXq2SM=a~stQz$IBbw&i3Aw#)}^Dy4Q zej9Klc!;JpvflIjgoEK?5BO|DhCDh1K8Y9sE4lUBP5X{+!??@Q2B5p_Q|M%iqr33( z?5;cHV;KKz=B?8m=PZ-()X$+S#OyFv&V-_N0ZC;K*$kY<&7asU$Ps&JC2)7+EJugU zwa!LE#OvXu59ZJ>a|V1cP);rb8C5u19kUpVpqa%B?Ns|j^m9D*+QuF$Pz$t%G4Mw$#4k5+KU@?NI=Tl3VYtL7|25axKgdvN*?t3uyum zR$xo*Ds3@F)#EP>KEH1fjuuftpUUGpteW}x^zaTDUw4SgOrDl7J)N0}0TkxRwB7_z zIs-i?liFTCbxe#HX(O~Ei1M{QG5cc@4mBLo@uST+&O0|9{rKu_njPD*r;Y4$RcwtI zCMp_KxG~&;8=|Ah2tfThCS1j4N5|355R>f4>q{Y3r)ScGQ8v#S{nAWdHp=n|5eAwo z=GU3$=4iI{w`F_BuIgYU?L2xmE@X^cn)4iu5#RI*MtpG|u?lQ7%Cx3+dQ1Md|D;Ee zdZS{Ikhn1Mul?sInCA{rN%LTr_`|dy53GaE>Cm-J$(RokCQ@a`7>%!0zlgyx29>yu zMOPy>M;kq342B(}LLhd~x$nwj&@(iolbKyM+QlWHMSY$*HM%aBtG#tM-m=Mu(PBHu zQKeTHj%bzdi0@oHN9H{W>}a*a8qK=jL+Gz&{m%XEZTpVUQ>a_LEe|$$P+ov`S z13kg( zNkq8Z=%Cg*S~udI`ix;0EwmLn*B;$tSf4)iIE+qw?p5mw8hu%ouhpz36u_|;1Ubth z%t1YI0#vWL{rTnlda!+4nMQ{|hdeiJB0M*nMF6(v6fE=lSj;Ek6b#Z9+mn5Y!x^!!@C`#T$d zJZ1}3GLz)RzDUd(d+_9=fPqX3|J(r0!8{6dBtrXDdQ;E!8O-sPGHBQ{^EG?lB^j^D z(QYn0{9KNXJ;{qWiq%^hG$l-6mFM~6&|UGOut+Rn{vZZxoX;*nk^~8mtBEh@%582R zzV-?7g3gFg$i)zko4(l5o@buo{2ZePSxxsqe5f5*2Vz%Ld?0nwu)M|l7ev{i4$%Zr zQL#8??qH}$_IdSEmng`c6S}+ZUEyD4ZilW)PTMut$yfi>=%t*O`N&acN-Qjd(N7}P zVtB?}BPe1vNF#K{_FIvs!J?vG1Vy4@aae{O&koKV|8iO|VwQr&_~#r$X=*#0FIx5< zFu)BN&PxOJ%4m)o+^;So1Zrc?i%~sa99t|_yxhbr__)$0O5C+KKgb8gz=n7!y zh)`N*oS)8*Blw$I=$HE;i#edE^DcnXmD5I5kA2Jy^v=3QC{FZNcI#In0z1NV1`PO? zHoA%fCKz;}JTs&FdOWT-)yl^j6y$9Y>Lq^c{b;_BgEZ$Q8kp$DXwi@%lg=fGQlhdv zDPEpNu)nWIED=?+H0Q|${X#!PzGsM%(d%R$b9wnL=_GM{6mG}pkHV*#F|?+~o`QZc zL&`DlZ>ybLqZ5`7BqJ9VjlDw%=mODBZS^AvAs7Nf=T;EFL%Xrk7IbgHi)^A|ev{x* znhU~c)+G0V<4NzC+16~zeB@kw>4S;EErs&2WBUkxkqwF}z%fhi^y&qJa!8WwroFbZ ziSUiq@bhs!Mom&m6{ki>!z2!@{=_11f+2?Dqd_qYzrc344K;pgv>m0SYh;epi?pHK zJw|)nvX@<;0g!DGl*S2k++iI2XJ`t02WVk8$SB~)<|;NAj=^$<7gKus&BI8JUn=Hv z+4KZp$Z~agStTpl();uR$-0cd3nz!~8{FUK&ieku77g7VPcUH2CG#pb&Y24s@QN%R0=VOf?Lz1<359RJck5VXz9fC=cx%6 zhx*Gu9J|0=DsQu+OAEIEOqvbNsG7e%^0!Uti_KM{239C)g&T10zECV{9ms1S*&0gv zH-FO^_0|y7Y0NpL#qe9oiq|d?)6(V_4B_Ytw?)K$xb`6i#2|tQTjZtSEU4yB4a=fV z0;AgcSsrMB=GFFrs+ae={-%Ej^7$xs3?JS^MzVS$IBIygd>qKiI`f>yK1$yFd%hE{ zq)+=x2GY4|Acq2|$sVwtu8hxV?CglMYq(T)Gf&6=@YD*^k^o4~uPyd7IY*n@E)uHm z8C@NvE@L{?yM85)>4eZ-98Ok6upv^F)9l?84_4z zagdA7ajA%b0J4*=yo}!NiKU}B9N?_g50W5oPyAQQ#cO;H)}McdgE_s@HE7X(@8#kP zUE2ooaD>=FEn1R^I~Rf^QbBTjC)|x_kGUH|ApT;Tg`nJ`FxP(3bI59c(?l_lk2!T5 zy)7q;Z^{u8O2%Z~&_G!qo1xDqTNt5B<$n@8VP`N3jpyC+QI^aRr5YR9C$y?NW2IK)2k$r;xrMsR}KFY*T~;M4jf{k{=Gyu zJ0ex~CQM(}n>H98Np%~-)FStSuC$3(sExc+NxLCqBrdF`W5!o?aeR+1`rzRWedhX_ zWbSq5Sa;X2V`z9NCH~6}ZhPUf<~&QG87IQWz!(Cf5_wLG5d=>{NiW=9<7~6zd`$RH zYB5)0>LRT~lCH5clAX_cBTZwdd9;S@WBP{7D5w1cD`SeJQMt}*HzfeuYyWTe|7Tpg ze(uc8ulPz0Lcp1x(a!xTB@NfnuMDeadSh{5DP8XjY&ve4n>j?MNo!SfFI;K=1yHXW(BEs0iqO&;R93>it!6% z9_@rK*Xx?kyYs>p1C3+bBBSEM59JtGt!?1BfeY7!;@HKck{nslvF39*o_LprHAHb~ zb0JU2TYT#*1F(e9dQ+*r{AY9q{uMP6>sJSE*xb{o^r5*~L>XQfTNnGqJd58Ho*KOd zZg1NKY|Jvm)v<%ZxCacA%agrudj`hLv9$MoG6N=U;kUz{$~!4_iapmFAv9WI& z2z}vp9ixTk7K2zMO$K?f;NAvlbhv$g_{GF@o}V5X4C@k2fbysX*y2zZ!>kEuqSR2Y zu7<^4l(z##+DX`8pl+pHTHVj zwJ(%veSfdQR89b_;K^Ra5=k+flUxMlHgdobAvGjtGkwI~hE$RUUH|jTDZ(}DU!~k7 zDcrH*TqD$l<8(WPcR8Uyoq8B9Y+IT<&|3xW$#ScrcPi0^YnP-*@32n~X6jUPWYMnf zP)Jd-$C*I&aK3uLR)|HT+ zYZq;9&vP^E3H8_VyDJ-rOsb3+G^si5?uq1LF5Lb^Y(s_N(NEnU;{(%J01bi;J2NTE z*L`m@m_)uJ7ULXX;<=M36%4N3w`|lC(!U(&Db7IQ49z``^23I_5L8k|$PVPq-EEWW znJ6HrQ%8bSwU77%e+L%pzj*sKN?7usE)tG%3Oc>UdFl-|!XTF61od{&tIZPf4JS_u zL*;-Xni4)#n@YjZ>3bG9v2Y1>1V2Z{t+TnUtu|RFbO4X4*ffV9P+P5Dq`KxL!1s(E z)-cRx5EiIx2GBzfh#y!GgDwEOlEJML7m0IeV#Fwh8~19(YW>;Xk>t$8 z=q>fqWpI-MGVGMN&kUDWruPRr5S$Pz_g7LSCIOSU5@G)kf@B@ZFljpo| zXOFIH!LfKt{Y^lj{SHUK-yW#hhb-1LPUgi)0~lWxYimp3s{ch7CN`xn97sUZVh3Q8}w$S?$f5K8leo&!&M9qJYT*hN>CcV;bb_V z^8GlDPM$1%*Uc=PxN58$WXv`5OurTqEWHK{!mN3T-pB5~B(=W+9Lj3K(-biOTL32D z@!$Gy_<30H|5pDEH5nS|n-_F77>+LT<+RNq3RP1}O`?0LI>z)uMOL=ZuCfXY0ZbE2 zGf*t7t}&p%pT&_;WvaQWp$GD4Kmd^jiL>G|Ima8uFEY19_zhMr0&3g_NgH9QvqPW$ zNwF?FqnrVjmDIvC%}|mm zdz+LkzplnZ0k#Ih;PjG~;p>}+@>v>L?x9$@XvB6aaLuToYbKLTGj^kS*~TW~G1L?l zVBnP_1{%a@RD#9MMpmqtJYxPTFrvP5ag%#v+eFEJwakRMYWeP_Ql99a$kJA9|$`4+=CXRC&iX-Ouo{lKbuOhiD9^Uo^OMzIM-eX?R2to#2cos2V~eI zo)KSc`k2hVec<#xTcC5!AxSA2fZlnk^Ks^q6wLrKb|bH}q^d`kLZm(r z)}>0?X8fGUtnUySD2cCx#JHvgFEAgxkqoJTa}bL~gXODJO?l-H!j>SM(8kWyTnT3#I?!754|kC9m_Y@`5eC(-4y{Po75;&JkzqyVM!z zDR2c<8K;QyLA;adD zaBgL3N4{_#eF$zHoW%)*shI}iLTY8^ezg}5zkiiDm-`X9BKWYPpm-UeqpY-)4O_>g z^4jV9!;;bT@swx6f2PNQox(e;40prJYv@+j@H33Tf}9xn-DUo}?(z0cGZ4@|2C{3D zuij{Uv zB0y?R0E1ry15&3Zw&AUUPr6MgikukF>5UbI^lBGemtvauoo8?&AaP8&q6q(oZ3LJH zGBE>GJ=cvnWn*=RE9Ghw!ouk(rADG+YpcwX^KodO2?_Fmk+3B>kG&BZ$$Qk{oaz`k zhPNhrAN>h->%!X7{-NO+^q8DqAQ(%8*~9|XqTuo7IYyzmrYC=^o!gNKne`bKT^xZfU|q^{oFbsmNEFlWME zsgo$xu(ZgLkumWN5uS4_@-T`x4~DIaAhSW`6z}j82Atr?@)*e7Z^VQoo|Hnvv@}l^ zWn*uhVFwvPY9g)O2n0mm3 zo*nKF-1B`<#2kF2E}umG7Z&V&m}AW&@@~0&NYBd9 z1w}g?GJ}v(XLER)NUaP495dK)Z8GerTW>_SN-H?tg#tU7KB4}G@x%*f5f9W3J9|H~ zh~J?=3&a4zN)v#G%xKm82#NDkh!IiQk1@ZkwuYs*(QQuGg6h(IOZaJ;I&fk0W?3fJ ztx)0vNs|j<#z9^8(~<`ObcUyz1R0n)uziyk#JQonY60w(<55pzt;LCPpoSqNp~0!a z8<5Pmw=|xUH1&kn6zYUsnRz^BoJt~i@MGL(jwLkIHwa9nFLIl%A=AG~21jJ82iSLo zufc1RRQ^HU#oChI9)li>;g+ym0MpC`8u67w6I2maw~pM8aMQRZC27%?qpe;R=1e(% zb_~TYEn6dvmVpUkz--bX5x01eF)a?}W58(m8zubgh*c)En-(_|u1cmA&iwAKhSF@I z_NNMEND>rEMmexE{Ee6~8{QaA$EGW!r={eQbcYrlWC~Ji_(iDLkmQ*i zmq4C#?Fktk-mUBkr%g2OeKv_S)ocz5%O!k}$~dGFr}nUMOn543O37g1`P-6$gqCp3 zjPUG18G%^N#)p45L}BsW2FVW3iPP8PkmBAG!#Bs4a>@^kt$`g#D{*-Q;zCIOu z6mm5fr3H;#4ad!j49B)S|A}ZO+B>N>Es~2Gnw!STK8{Kg%x#;!b7UVg0ej#kPyqQc z_1o~*>E|LWnA@nFqyEcw`j_q&@TCP_-V$y}RSRuk2VFzUM(q>@W7otQ02mH{0inY# z0zER5%H6O;NR@vE2^Zuci_zr$a(Rt(I}#>41m6bQ34VRdbopTvMu_-V(ClU5mY?j}6MY+va)_B&u!h7pL`44L|9F!r-ZQ-2BjHR<* zEcA{!e|ttP1|gw!_~{hU$AOOUcOxGRJ#7i{twL){9y!KY6d~MR-X!L*g^mZuw{3bt z9l7e|%UA%;)KtU>!n%(BUsUsgZ=#sMQ1hWAMS_Vfqhr-G+L5NFR|vsb3qVW!y{iZ@6*g|>V+p|UAzeL>7o4qbkgosG!{ zxw!AnYCwU_BMwI0^zhBp0x*9p5mROcP@~H5T+R3GM8gKFa(jXVHy8|tW}`%;L}kk( zOB9-jgAln!YF?5$R5}nn+!djKEgR}MWmKtj^twY+;zqMRK8RDaPp+so9$adX?O5AT zn(vD9vXl}#!Yj!hXua?SnBWZYz|BK1El)7Zp)pV{4<}66JSF2*(mFSAZej)vs3Sj= zyY<+TLZLK4Addc};SW=icoM~MS!>0Hbev&k!fI-2DUq-Hr-YLfW@dI9)kZKs+nmQ4 z6bO(*orv~fB(qT1brzC*VWNr=FPDnrmHNYkWGc=*@85P-1GBPUt;|ks+eaK);MyG4 zOg3x*uKn#2UXpWYdM3gKym;&YTo?Qm3{)so-z>oFJ6gq)Ck6Qn9|#9qxaIv zpy&?aXU#P{V${M{Q$KRT8i*A5Qr5yK-Y;8QZDkZ{T z@=l!K=9ypN(+Z~w?E~Sn6*gIw;stxn_C%?KM z1qE{KZp8Ues^&U=B?7VWQ@?BmH6xRmUK1JNGGO(Mjonmy1q)0Xs7<-A zBW!Cp7lNu%qKK|CpJ(_b=uz>}bBN@^9TOWMF^C#~(?Z9!OK7%s&4#d{0w_FV9m&bZ zO(=lhBEf~ zx*6McFP7{>6a;|`f)pAb4qjUfvol6V$gGDNN6k!QDww?x{g=#47R7i zOdYNHwdFC>i29+*Ti~1Q$c-kz`@fm_EXak4wY9~zWb8uuL>QD;X*PXeGb7rxZq9S2T=E0q{x+f8#GUebW# zj+vo?$$*R);{~P55@P1?srIghurS*P3ulD{3vgDZo$6}gW_1Qvjgzo_SVV3= zr+-&GDB8U8m#f!g9a6bI#y7G^1U8vw^a;?KuSmcrbacCr47ae@tz}5My3T`oJSQkH zV4)>^X8biisjexteKi}TU*Dc*=UnqKd#@HoBgHvEfSyH9~2djy+y+5ak?*eDaTJae|zPhQ4 zE0IA1JNk3O?=Q+nUVbFD8L}>~i=8Vj!rV+rws&0uV0QkRlyX34TlsAh(ptnY+``h7 zA;ZS1$C)QvYO3=PqS(I^zE!rsNMrWiu7K2>IDkoebN%b%%qoT0IRWb5x$vE;9`Fgj zCy_hWm0XjeHFU#pyt2 zk6x@EP|PFU;voQ%)Ma!lC~%lwwWvb^1pWdMAgHLvi&H5^ zSIlV=h%2xd5(9<}nL|_kD$nRY(eccwuHkcq*J?0fX+u&Z&=!_Z=1>8kuR3uR>;Pc^ z4AH8+IDb=Bb!yARk%15nQ+X3^8Oqp{q$16^h0_IQ5~nF&smk8bzKr8zWcu^0ulxbV{<(wu(E9-Erb5Up zgbyH(*=Ka#;kd&mVU$dLx{Ev{O&-aRC^jtG}SBkXiGA03}d*Vt{aEKQ%VXkKR}`_YX_6)-^B~J zJABWym$r6<&6WOm{Zx;K4KNTcoSXoDlqYAU@DDn&<%2ErfJ8383MK$d8q@AlHx{Rt z8ZOs+s0zHd1-s&R(IdjORg2^<7_7~bbZ9>A9yT}FWJ$YuJ=g&oat2j6vK_7UX3{Hn zgkMg$gA_IUh!X~PRVqdPLTh3g2lBQEwB))%xYb+@{JFc?z@owM`sC7F$LV{TVA9?K zo{M&raL*Ve&o~@BlGWQm4#(_CyWlEV+x|n&TJFAiV;#&MC4t3kUbe@uMupRsFmd_{ zmrjp>?80BGCV}(;*3F8^VM${(YrrtltX{>>$x&onU`kH@`P*2^Hbf()AJw;x@VCQsK7__;8Lvv+xWD{?_KX$+zr9WUE+O9wKdwd6h^;a$;Sv$p zAy)|lkP*;NGyy51!~wZ*X9gncAgQ0&Sm(AcQPSUaUU<1FQ!wAP6^n1Pi4Bz4-)!vd zBA$Iq8JP60M3dU9G)jo4*)_bANwV^TVZ!V*>rg1pow{hIa3$>>0;WP$*<}h)b_sz& zQ3$h4;V2J1mv$hT>qRhK;MB4@S-2iAHSH|PQ_USP!FFz=2qyQU7{%6iAbN z;pA}!M{)tm^(`S^5&l!p)H*^f;UP-l&o2+9S@7bH*-de3H=v7q$Ysla)6&!$d5MF- zFQG#DxwCpJyJg`5e4fVDQuXd^{APyPk!qSTIQ=;Z9WJk{fhva@#T%m7binVx%0Ra` z5{)^K{6p7chYDCJ_TJ^r%V@H|L?0NwGNIHIx=khr8EiYc(A_Yk0RhUQEo(5G-#$;} z6NAcokZPL-rFPi4Y|q+{C(((rVeEv6O%4A}%mD%z{y_?hwk<#6@dRWz%%3HAyl5tO zq5Wt7_)twb&BdzuH9j-YN3$NxyV4FmY(-BlJOGV}BW0q( z=`+Ky;Rrfo#MAhNV9#PWelnV%OncodgbAf+`4y6hso_o~?lJSyAD&I57y$h{f2$1R z4lBvw0w%nUaCJqJUF`~AJ-p6-vn;G&HvFM0g)osf$kt5vID?!Wg@tSTvJ*N@6f*|e zau;sbFsccPFThvTeho;)Z-+4obi(P=GcDP?(9P(!!>jFs+a>g>m{Ny3298!rZ*5G@ zDm4isHNLbpx9#1ANKHi*a&HS^Z!qIPn=%*pU_WblQAzfZH7H>mQ5b~uTG7kL< z2OJ*WJ-n3V=ue#M-MA^5IpXvQM;6_{>_`cU&pz-#79GP?Z7+YfJFU(hVbY#VsZ{s+ zv1Z%F968MC1r5pj)kprWE+~=&VQc7%71qYL+YM6{OO)3i;Sm15t`3B#285#`MH`c8 zb)_(p!@8EGXicsLh8!y5LARMMAi~xo}kl(#P!nhm9gd{OfuC8Y|TOSqrQl&gkde=nTE^se#p!mYfW5n zbcu3sfqjq7F#2xzp6B;PcB)#0J~QJP`@=ac)p1rawoD;>*c1y(Ktv6xy8YEGphGSn zXGD&?d{Qbl+YO51fb!ZDFsMLC%79yE^r5fioa@*x%57-c7zzg{gtml3x zbvb0@wze}D{@T)n0E&GfE>t55$5vK51v#)sS9s7*1q~zOm=&g>ZYX>alP?z@n^;o3 zc9W+!T1v6S>)=IO!i+Jcj{kS2(r?hOmMys9%IRUtxMU0RMEKi5rG|OTIl5X09O7Sg z;J%%u9OVx(;l#EYWI>2?L~tIju9GgHkFxps;kTLT;*5DR6W!ff&P*HNuo>QBm_IFd ze-f0^Q9CoCJP=N6Uuoh5e;rwk&UfrZh3aZzQ6(4%mo$YR*4Gi2hTEyAG z+SEsA`$BJZ0DbjD3$!xUfz7Fxf%sm>TX9Yg%18h%^J;P4;0Ka+erx+SS1xV-s0 zf?!q4kf`a=HLJOawYP)F#?9@GbH}DNBXbVrU9&l3@fQHbd6cAwM~*}x&0(6LZQ;8& zhXld;)i1}#1QUU1(2x!}2 zXg5HrFj&8!Ea`U-m5&&gk!$J@?h1`Z05J=5&1mNf+NJ5=o|$%-d6@8p32s$It;w{Q z(8^c(JsUrm95?ttP}boTzJ4xdH3D4Z^C+brJv z>caYalL>il_`#fn?y&G=T{_bCz^V^SH+_{Ql@jN}@eL&{Ba)^~#z6{vY53tGNoaFd zIPtKOAw`-bpE3}>JR#}bIE2j+J3hXvM+gwf^m5+3yQa@CB&*ARJYCsVuxIjyD_(w9 zy6e1Zta1suEBQVQ;U^WMLF`7Kawz)b$m%zJV}M{;7p(W zvnQ5h%d<0*ZBA~ud$x)X*h6xyIzs+pAZ)Ct=6$^{yo1%DhHi-f4~BzNA&DA^6Wtju zXqFpqT-YbVV?vyXI6DsiEt?%adAas!WI_Qr%VnZ9$qzfFVX|>=by}rWf)fr#3} zAj8~>bfnK?H@!Trx&`he#WZ@ksg7F|o||CPy7s#jZV)u&wMIu{Ei6p#a5QuOP6${nVp@F9l>m4@5>m zxgK4+m*=)drZOnTm?7CP+@RXRwIko`>It7Z?EE$6kIVHp9|Eev_E!0qvhW|@OuOe( zk6b~c2MtW$yr`)eok=fA7VeEue(U6f*nalLkHl%8KRhuUQmKXlm4eyiP%#Q2pGo2@ zw1tf`?F&;jxb5184rOUM5H^Y_fn$>hheOYoXJD}M*8VVSyzU5$WkpX(H@WYBn4CcP2){hBPc>R(1g6mh zrO zaGFDJH7YNTuMait36B-tPUp_6Q?5{GA%!r+>B6dkP5&NkE@4Z!prcPvqM=DVieCcJ zRCNqzj7Fv6L5GLrVEv6h4^A&dwdk1+oSMYa!!@jQH4dM7jdx`Pt)yOQ5L5?p6aUDg zy=;LSE30=H_oRO5cpo|&K2xg-@vY%s+`)vH0IJ}m+knS#8|eVW8Qu)NUJbXc93UujId)f(!HI>=?RXji6->MebjODG6*LY8zG6XtgxOj~A8HrGTc)oUv z3Qfd*oqH8o>@rM8D2+lN8lv(7gqcsoRxE-@Ro1GRMkPH&gQazTv%Y-T#X$IOBI6p% zjq=<2!U{tXyLvyvf9wrSo+big>eyXf^^cZ!V;*8tAvbEr(9Yf;PME$JP@=x&(b4lH z<8UWM2JE!M>$DL;Q`PSV!$tMZ^>=2UG78&T+B;qPrF&9gJaQSt+YokG&XDo2`Jqs* zL!eqsP(4t+IFf1ZEKlgL4&OGDe7w9WC{+Q65TL5q` z*jZLv<+LM885P@lk5Acwf`+(mmY86P92};~LA4atXXHmfu44G)!O2`6jB}$9gdtf{ zDY1f;#BIVcGN=Bpoq=BN(R7sEYz{ zWa1P8!CQ=-8d9-8Ti#w9Zat#3vrv0M_+7wD0xaR^IVqe)VR*nncwtPFjuK)yvKJO0n51tS%oe;?!8U^40j86Te zifTG_5#1jMzf5_AH^wHAki(CA!tpIl?r#Tb8frWc?Kj4|E`tNz3^Vrw3;l)AS6PBL zQ3n(j2ea0L;n%$-?d41QAT{XkM}|AH3B<1O=y6GTZ)bRGW)JH`L`8lF70|lZ-q~1s zWFr2o4-bM5FHM&}-fd!gTj-wDEjm_-)SaoBic(@%C}_KC?dol-izDv@>v&_~pC7|* z)BDgFRvbsEMAOPZ_*FV-=LW*lM`fI7&L|(gk5k@J7;!`f;Tm14Gn|(Vg~L(|WPk3$ z*&{n-5GVpjchaFw^``cbw?v0$fu;)u_5hG!Ov z?pKt@1W_+E^)Tg~))B4Mpgq-ZoqK;fq}#6RcBJfrFpWK12;rcTw!<50@ZUvyke9G< zyoxojq1Zbzq2*%jht)#@L?~5z$oH6=S6rJ)p^ux%SSU|NHNnn-Ws92l5ZnTXD28*t z$TF{Am_h)0MI^mCb@BvpeRPoew+PG(cGTYMJui%*>E*}W$DzTRQzoq!UhxdCl{u2C z5V8LtWRqqcLUzXf8|e>^;SofHYB-$aO-&V9q7#FPltb}YDAr)CJIqQT~f!v z@QHRjE;OU?(u8oqcs@?*a>^vX`?bj#=F1uE;*Q9L41_5yoNIXX(8YMksuh^*G`TeU z191ovw1+c3=CO_+myxbUT&?`;$*{XQK}F+%ond)RSXp0#`J^vGt8w`HA#B_?4iXP5 ze*yWmoV2!$2rxMhB@=!8pc;@DkOil?YJRNu%dp3#qm0{7p>n9Zl+}EGagQpio=w`F z8d(v~U{5ce*n>CX1jro0#;jY*P<>r;o-{gw=G?869X)_s51Xo;=F`V*u4~F;{sDGX ziMaSFvy~w}e_UOx^J=1HvRj`ytXjof#k38_>to@7Wvr<>*qj>Yg$7n%gV=OlxpcJK1x!v0V807^)BE4k~pkq!VEZ z-<(&{HoNr5zBX{7pa3YSFIic$t98-~hcBX)fN9elwNKUiwM6o=LuRIz&zbOAe^L#v z`Tbp*VQ3IxEk&gRkx06(2SFO}$H4C!7n7#bqNGMURo<6$TA%w>2s5vvU ze{}PfmZTUgAFdwV!xt+zQJk@*B1Is~j^$}8BkF{!0)B!X*9oPtu+&;Devz9}~8yhTauGC3ke(@2lO^8mYL72pl*06bA4a`aA zh+76~!6bEv4n8)#(P*kM1=GRsehsR0SNLnHVt&RMiE<&m>sP3)0=Og$@Nz)4=SE4$ zJCfnt)=~x7AB|4JJ&M~_*J-^brxInpPzx~8?w?VL{HpDB0(b#&ewu0s`tpim%tnbRm#!)c_LQNBs^1IoQaon%- zPMsv;AP2S*-B9^CA(;lr^2FiRiH%E7t8wt%2`QTi%MF;Da%q)hi0FsMsAbS$)Onx* z&K(I&)A|tnP-;L1h$WI=2=ldXHE7y{fD$edUtBi3hWZ?<4@M8qH;a9#{T0fuY9bPm z4I}HTp$6WPLO}6j5&gVqO#O#rdW#u4F93(Gu(iX)>(2Jgr-s{SEXE|w>JE=mDvkif zT!J9|%`-mak)EisVM~rUeNh_NB}6UHb*9uZorqtCq}$RNS|_Ib^L#P9eh`4muUe^J zh1gvKx&JnF0I27Cx#HWw$rsTm>>Qsw0OTb*1HH~BWcV+27+lXhw%F`2V z16c`wkCVaUq6D=f0sAdM(GF21{?5d8gVY$()%5wvE8iVw)HCD zi4Iord;yrYfPHXc>0FO^h2?Fer#Qno~Dfwk(P@{+P6{$pNpwxh9g zKdu%jg<)ZPf^1#bF+u(2Aeh1MnHVY`I31FK>z`|{|LcU5 z{6@-}P9|g#p`;gt%?Erip&J)Qq(+lV$w3!nfB5pO8sf>SZiuJh7xm(y>#9oov+wU} z(l&?0j%uUK7(U3PA=7cR?uNrt8XUxTbxUg4cP#oRn@gzWkY!8s9p> z81{qCrDKK91dUYV)VqhRtn!89rIQ2=@3q4lfQl-wET8SY`KaMn(h7U_w)K@s;o@Ws z@8grMvgC>j}l?IF$LuGq~T8BB~%$r(w%wHPXg!qwx?-|iT?-}_i86L1lv=FPy#3%4qE;hrr6O%E9Nk?pf4S}+ShkFX-vXOu>QLy~up~vioPNDz zR8b1YpV5Rz5EVAbdEmeQBoY30ihq0hT>tO;xza+*eV3H#&q>y~u^Z~b_)1sd*<&%5 z*#>2Ab{ID(ygj@86;1@507*>y9-A=~CUqr5p!K`U*nJhC!>4OXP^&1va^_Nv(~xeY z691#vr(mLN$DsYVJAC!f8UPCVg$N`v!d0U0ZxTL88$bcz*%7n*H2Uk`aKDMI$oCc3 z!uR^owL!}gVU{eGWt+9;VdsZK4+WjHLsc@uVi9^SY+I0Kzg+*2m{clSzag2(LkJF- zrR~qZn;stTluxNRcZB=rCpoGXR|z1qx7{pum8oJFyRejLz3K2${-Jm%My>|SeE3{F z8V%B=J1l=zVHyhWPNlk~Lv<+In4VCFXd)DqLk)!&W`nKP?hOBGut{y9X(&ZPwyV8- z=LBx0Jq!*uv9~1uAV??`VM}=aHitU0<-$*A*U13+eCmn0Fr1Vh+&MWFCCXdM?`!CB z7ll$lT^JeQ)%9Ok6Q1qBP)`vf1VOGUU_yj{%^Qyo^^~GbWz|ukDz}&=(+7ozprj<}2Vx5~I%@keD%j{-E#bwk z@Z`9p@}k%xg{%TTye7RsDH?qcz$9knT6Kg_W|59bBxkns2HlmiEGU0lTkpHbCHgJN zleVhsFruF<2Rw<#Btm}v`jm|6#YC8Qn1R-)up)3~ec{~5cfy);elX*Tt-YjhI%l7H zbd5NSLZLzG!*|K%5~<-`3BoMl-wC1ZVEAL7V#V-di5S}cUu`nd0k^#%bot~drGs71 zmqSR#o?d=OCP8v1{CZKCGEIMeF5H;`vB>rWtXed5Qt7okCH%gz3CF}?(i*Jdr*Aqu z=2Dh_xsX!qx1-laQb-}(HN8*G2nj78HRl9x5cRelrD=M$NC#m-Ws(+nYhz+uNp9`8 zN(Y)poaC<{s`LmKHp%K)6>t_tSJ-w;X?y$H9SKaP@Yk``;$yB>562s;O52;SO{#sd zx_H>R@aEiT{W2$+ZjKnQd%QhdzPuz~zP`;x`}C9~7Eg0HwHd~^V+);Nsc6 z2QaltJy;ri82bP7^PQW^vnjD#b93c+^))iJIIKXn_=BTGr(*g3^4-Uj4z@Qwkq*}l z)xfE=(r8Z$-=5>tXM0QaUOhIDf~NsGPCv5zSN-Ao`f9PAfWt3%rL6GRl2*tiBo_%h z7v5_u$qj!Y<&UDnrx#XB7aZ;Ofo;o?r`pQrPflU5b`F-mJGL+?r3l?$XngOG2=3v7 zG>3C0#fSOM!KKZ`&Bw$TxTj|gZCM7?=Tf(l7gNcBCri!XTxuQ7;jM|!?<<^9%Ax$E zX%*ELfSuhJj!2PI0m&Tdu+~e`8Gdl+B48mRwR{?~uX6}t4Ljwf+d|i9y*^ zao2HzLg*e}4J)Jkh{D&=7mizzP+BYBdt?pB!{>tXT^pkZRbstY3cqA9?mH??_g7FH z)*KYRSDdG;?4Z&+5Rr*vG~5=#KwU3og`Hh`@eIU%Jn<~uJEvMC%o=xS6so0%87;dXh znBNiJOv@{T##lR^8+&%!oP_iv95}6U`@)pa)$2TNo*oTpWibqA!c8Hi8KgM0`Pn2g zV#nGyH{TNv$X6@F{ilVOVmQCHY<7PlK~^bjJ+c&$Z_C64@=aJXz6rptMVyr}IwYJk z)%RpWU6l`nFctF0CTX1%!_L0ajzZ%N)fCqh(ZpKIQrz-{aiyO0{S(r$C87=2|8UZ( zv^%6|T1D_F)=o@-KEeZvjhLOiPiTAGNu@;NF@phQQzA6wK8oVvRnd;X`7oG5F<)6u zr$p>16mM&&KPv?hN6P=)7^({qIjd;W0jgnC0xqSj4W+=)@eK;H`nrWQlD`LLr~QZI zrf_aJqcI$GdJ>nm<^5f1Z)#5WP7Qk&FbhvasjszV+s+ENjHzx1bM_K-qYlN(@N@d`d@L*3;dN{c4jV6jrxCT^m zG3aJ2(wLo{-Sg2T-uO`XSa~?NWif^|h&hrIX=GwIYmLm?o?E3j)My+F#!>bhZY)!7 z#&>FNZETPqO2ggkTzLM3W3DQdxbGgFAOSt%?NStmpH+pA9+KdT$_ry#xPp^}d0zL%%B08XAJ-H0Mui=pz!eU{n~1x;!$Q139I{OY%o z3f)8DQ#Beqp#?1{&;P&9&ICH@`rP{yn9_g3pC`jGNhUBPlQ4u~62j~Y1V|uYz_7^{ z)?rrm5JI8^1c`#Uued;q76)kOBHYH(q1)gtxJ2cwXNF4_w&HLZ+p%? z?|bgK?dhpPGV5=7p6~WVWy}Uyyji{;%w?$(1>i)SZm1#bggSj&}o@I`^fY@*ykMwN|L< zKTndE(r#_5A?*X{rn^Bcg47Q&V?S2M&NML)9A>)anQ|R5|6jZu_5?csM~$ROSp0PL0&WZS#NdoB^XE(Mp56OiE^%$mAkEdo3;cDL1kb?sgnfCfy}3)g{dGLnWC(=*P8j z_v%*80M#;5RFE2nx0tn^mI=+iFD7`nqKH8NUJ1$8p!{``mGGIXs^pbA*;h=24__hk zZIHARF2-he%a=nrY_n_G5kcQ=LkaQto+aFS%9@Y%U#PhDo}`tgU}xF}5r#PHs};O5 z%=$#7DauXE=d&KUy~2iZG8C`5tWbIvY389{@^-Ci?ptM2ki;Y&ru)n(br+J@3@rN6 z+#-*SrbLZ2eII~V#$`|IJ?rg1j8G)rUt~pO^7{$I&B8~>%fh>UcZn+)2O9&Ukb5Ta zfqs@~#QZ=N0LDan3K$oU^=!jG0c^9HHi|ue%D%9%nS%~|5S|m!3%42B zIdaJgYW_*4yuH{#VMR8o0>6yMB?`ANhyW5>Xev-Rsy_aiTh^C-c`IUZ;_RH{VrQ03S@Q8qqu{$p&!W57jbQ4P(q-9 z?G)K4IlJiik)IIlNX3Y9ha0%WZqnKNQzsi?PO!}IlZ2dWSVbBehm{RqzEX}&vV?v2 zpCju%hKgODp1Xq3A?P)Xe*m3&`(rO=BVpF~x{i9~k#34ZvT(TE_y-ogq{5ashMly` zKg+;5AX$%epWV#9r6?vI3DFw*4C>md8HAaEHWBCnc1i2|fLn#|_CJjwtGMJXIo#3; z9zxHg#x3qW6kK@yqOv*S!toYJLI(A}#P`3e+KKD{CqrItja$NflMIds{5~ih5H%v2 zk4Tw~6<9Y|?)H%$hC~UA7Hf&tNbV|iu#xfW$Xn)VwW(WUQi%tEjuwV}yGE)RZz z=zxn1fg|;ld@{sfF=BP{B4Gr=Wm%?D^1^f{jt&&BWV`}#%hKtn%2c1&jh&h*4_RJV zYD_1YTQHv?=o8k)GmQ>{%iuRX%d4bxf)%gX z1sMmG4*-w++XyQ5?+LOCWWPgKLSHU8x!@mhA*yAj0I7hE@orz&9^x zuDVYe*KqLNTWIokss8C!#ARDXBS+x~NXdy&^5Br(9vLu&y{SQDcqT}CNX84EH)+%Y z_KP;jiN#n(QH}t~@XBLIEXhm6z?he-sF$&Jl0_g@rCn@4AU{g`1Rf<&OT1ouRT_Pb z=l}r3FbKt!H-}k6XzM)4GtmE`;f%`YRs(I3jHs~Wt3I~UZfq-T!R7H z3R4D%gbN~kx-Ug){3K!|rsp7r6DK%2?uCO zUMsWVm*r^g43f)#tAT%9ZW>0d>X*0MtoW%1j$zBzjz%trZZ>VBv*WJ5nk|ptgjjS) zN9@k@GjcSFCN6~;6>TqNcA0ehm^05Omk&eCq?!n{;0?XGu{ zFAX%7mnW8OQ^PMp6{tzzW-5AGYC>f+#*FB3ILQaLx(EfqeFw5Ud%Oc|Un3B`PGD|! zOAtf)iB6Re1c3(G zzPy$wgh=Wy(pkePL?4|%)mhRLAJ*>&7IeEQR zF3dW~W8q530a1FRYy{L#?DM=lV>6JdNZADCTOTk~4v`M_oMl(=EmUl2Hl{o)ggnJ?4>Pk(%D6PYZtcBJrjZmN{u*|@Z&7Oa(L>p z;v|?9-zN1X12r}gOqE4nD)p6Uo<69wa#3W47^I*2S~X^S$Xczp*iGnRH$0@HjQf0R_B1#W9pFvXba?QCspx1V+ra?`^#q;tX-L3uP%AUUSmJWDuG4RhSV3^ z2snu*rP8b;-yQajTqhMcUtU6U^!NsNIg6h>7&ySJj2X(MLZiu9OrCepz)&b(m@d^J zqRolO+6o(R4_)MfrEpEG*03};$Y(<=0iSc6gS-v&y}joS(aEqKeppcf7HYU5|h7m`LJVioA+*VV*UdJkIT20Hq$7n+iA)gCu655n(3%^ zU|2%R+RdtdX@iyZGJP4fL^k8wZExnl;`wmQf^%^9awEwZR%J>->Q{Xj`TaqxSABnN%i&_sxtflLlMA(&N+j0fhCZ_Ep_OD z33b6kssR6HT|(_K-+-V;69;&KqfzXG(6E zVng`_b@7A2L!1;*th*jYKJJ&T>~+74*OsR#yv z&lB69ucF6+OOF%-shmU20bL2zUK`end)j;lx@YET$aaSnu~Ekmei;v)&jdKki?Pl@ zy-r11zF*!hcQRB!=}WmXY^8&k;dCOA(-3T#wZNJT%LE$^USKt89czG0AT{++?`k3> zB5D3Yg#ZK}^G+k1TI^@_0FO@yVA6q#W z^1aY(P-Mw7AfpPs`sI*|qjOb<{DxyS+*@KM(U!=D$)(#t8xf}4F)eedyi#mXPxySo z;X)19^uA!`fpM-daO5gMb9r=uj4Y4+|U93D!qa8F>rTVQ5|H@yKQ1MgOYQP=CiKYb3v3iYsh78a21*pxj$s$59D1 zM%)O~d!$q{_(?k!F-)j4OcIBp#Ub59n-}w7{fv3pe3eZneZ}1`JVw`%jj+5aZA# zf)NEqYF?Py`^kL0IMe<||dls=53K{k?8|-L+ zsON<2T;PJd&@tb!-g_u)nZlOcP9+hynPe?R?DNNSrZ9Tr*8wZ;o^2&2RdYz@Pin;j z$m!3DCD{NQ4eN?J<;vkmo?sqPq0e@yD($)1Hj0pFsHUU*e4Z8!G*}-4e2Km0w=_&! zy-i3#h#QpKcDq0;*+(34wZ(GIFgNRs5OXYw;w@Y>$b~LeCt~sm)q2(WPjSfQ5|*NN zuOHGTMHV)nhg=yj!JeY_Z_PlNj@1BR3l8HL16n0BnK!`H7-O~veHFx`P6;V^*Tdf; zB0`IhlZQ?2v)5csLRK4^b(~C>?%Y#!*WZQm8#VB##o6<%pf)|s}666S) zQm=!UFu_UxxvhLbM2DbT;x6FeLY5uY;W}amS37U$&bxumO3n+hFm^3xlo6Y{BJ_ zhhUb_!N>Q3jvaRL+`nlsF*Br=4#Hr>v^xg9;LC&3m)*0)Rbi5t7?6wFOvcs4?KM*j zE+P9nTRaGMSf=DFr{t+9=UAGs60Eb_&S?M9RV3SO3f++(vCbNfHLZ23_>Z0PKq*&$)wThJbNUi0_z+%cikyF+1HU0O& zCQ-_#D2@$BCwDUMeLD_c^GTfl5XmtRPy(=*|3wRGQE%4@K*!E z?ip6TAB7K!HugkFK0~b8w~e0sHO@R#M1Pv^&5_wdP$J^r(!mEC0dYwn~bUo;c|+3 zQr+9+?VW@S^8Lv-1N>B99UbwnhvV`}2+(RIWPf^uCEj#Eejk$~MYs)+9jI^f;wogG zC#z`c&^{ue#qUjyl^j{jUVGpin!16tZY;ijI4JzB789&6FP~30XmAQ1anbrxET+?6 zPM~LfezcsNCy$SHDt-pgBg2Ji!{;^`Se2BE7B%t=csc>XX`HdeWkSby*$}zqvam5$ zZihD}5SXUfsP7cxn&sYx-X{Z-kyRAl7|j_&?w6_ceDBAWk^F@aAnYJa5-EOvCgOEd z7J{Fnw~i3ltK0l7h4iF50`01zvO-N z-H1$0xo4pi;4)Et6J|I?;7OtU5@!b^H{jw(cY)XDvqg(QOTh^w&2vpn!qb$L!fFBN3DQyR63Qi5?q2x*6ddO?!;Fdmf*l^+@+c+{_vjmDu z*bYSwORjgEhpmGij21a11ykwb@^aR6d3uR8k~_9bA-;tkHzE==F6gyoL9Y##geAPS zq<f#Oc8;(;CYfQDFkl@lARX_FGC5j@XtHXyC39Tt=Td_bYe!f+ zha#c)a&gQ70<1-sm7@n`7$<7PH+3~*(-N|x&dQE%nn%0)LWz3Trj7JM{!?=XbR2}d zTht7r3*tja9%lk?fW})soJxzXWFU%Q=7otFa*`9z4+W*TQPK<9E)j{2c0|darBnK9 z42Ls1AhrAxV8a4>ajXpuX(-oIzmQ4{dPE29am~=I>VQdJ$K+8~RHGkFGAPQ#+6zt} zl|ZwUcwD3w6V}3?R*tviCv~z8q4rlq=h1lps_WQ59ms<$2yQ&x^uGMml&AFDfgxFs_MH4>KJQv79}QAyT0I z{5&58pq^Y7Hr0wjWz1Npylfd^;Qf>izE^a-@_Yqbk;dzfQ(ZWW;T5Q+zV>SZ&aD+L z>I2w-M8JM>4mKO2S2cY!tl2#CrWuqO^2%N(943RW^77jvxqqAkY7spfo=-0-LA0kN zL8XKC159L)kt3KKgxVbv0BNvXWdH$DxT2$3IGH*ZqVlKpx}bMVZ2;NmwXHRE&|x!jAUC=jA}ysw+>d%Z zC>CI_RXDrypv>!%VH80V5ydbv7;K(~gi35@tI27Y?whn4ZylMdNF(uoG4GLCcceX_ zK@I%g*8e0}%Ib6TYMuQGg;#!Y5>Ug_G1+Gn^>Ql{gZyP)1%9NE&IHTBxUxvD;qRyme z$L8+P7-CwxA9Y&6=sqctk<|vaWZB|}MVV~ z-$zZ0qD;lk=#ivhH-8sjqRPe_#skaTJr$`lN6r+d1D&y!Qa>6iZRdJ`jUg`7GWysq zWGhH1Wauc23OFO9@;4N)n7Zb1c(LtRZ(GJRQe%UQ@lb>lbBA#`SuZBohGcag0!<=g z#S-mXbEwhC>$}t*(Cfm>rKvE!zcCWCdETPQ4OBkVe*17)*7^;$EP<#b0J%W zphv*Zl>~z#jy7}7jTi1>7YUm6Pk7_Jyf3pp zPg&`3>rBNI>KaW-Rg#3lFzGRQ*x{m9Qdle{x6H9*eP5I~=WHaM&0Ec!^7ZO_2j-CHq-~)GBi8GF7)eQH`Q1CF5ER z@(Lt?+L>%l`D*+;Y{p1`K-GeLLnI;qCx$b2Py3|KMgU>LR>)_8!vd)Q+C3G8DgiKQ zs>73nN{(PCwyuW2tl++&Fu1Pe-Ssxa?qo^4%>XGr0-f;;+u<+R zz>|B6RlNAu^Gx^#_R~<#^1mes0oCvp)sRYPyZkh-DT1ESlI}z}kdn8^9mY<_pS9Fs zfJPaq!aV0%IAMEwr43{vt3pd_G;bevKxs(C$F%CQWU34L9N7-r>76ok)__*UmTX^; z;eFg*1QE~=Xr`jh-D0pIA7MZmN7aE2FbtbL4m4sPc|jw_4V0=lPq<0YCBj4p4Tl;a z)SuxXhJ|g-Z5uqWm_Jl;7Ucnp2tbwQ7MRdqdfqJB#uQBsQ^E)g2}_xW+T4=4Gi;z5 z@7qLmbx#~Ef!eTb<83?|GMs(G=$|0=WKFSs^koLOx;zS31^F@ZTDBE=E%MuCE)t0F z@Y1gAPWXt0qSn?bEY37MwEl_yPIY5 zLN@lh6HzB*WOLj>qeVG>JWIBwghsPT<)%V3>C}r!xyfjR2OxkFf1GBF5l0o0{iQNy zyA`y}X*gMvI)kY9eVUNLb|#)-nMalmvB(MrF8rC_fp7~SvD|iZBa<0$0%ApD?185G zsfmlFUo_VPkU$BOft8o9=DN81NhGGG{rzAEU5YjUKN*XDrf;P~EpXHqH#Vyfcz_SU z59K`?&?6Xn@9)f4eE70GvV0jBL42eQy+Jyy`VN{t zq*>iEgV`U<>|9BWp)*s#UH`Gln@=)f1u=!@KRr84o@;Wz4-+vZ2g?W`ij#NjMyN)n z3vy^VI^T_(9l991fh^i(<*nRV=pZ;6JC{@!wLoj48^Z~MDvC9T-qbIS1gsjWE<=NUoaCXSK{@Mcn+d18zihO5+q1kVW$;LO$N+#)HD2LjV4Kf|YLI*;ytZ z77~$i7j++D>lE9^ijKd-eUX%VCaR#}oRuyvX&lwO`Hm_}q;C~*mEo0x=(Irl_|&JL zh1gPaDDSvhE(sYxh;nYJGsck&BPf4xTlu>7uW3N_#6rNtOlCjVZh&5q_!&@<+ZW5{ zXImmoR|3aO@85YaPsVr4*49FqiQ~@3X2JHo8CvxNunG=Pr`JZI()c z?LY+)rE?x@X3OEQQ|BD1a$)dfL!+=RD>6a1qBWlmwQ`>SW(1FO|2$ciQsJpw)eN28 z`wyFrIZw(VS|&o zQJ*W-786vr$@pZ#AM)Y+eoZrf*~=2;8{R6WaRMu z>BvVOIQAQ$X3`jCDYp&pnQ#;FS`r zsAOVeTmvBoY-(^=!iguX2-{EGA@^6YqCULE144|h|OS*Ii8B^ zOpe#6oL^i2=}-fmpgdp8ID1X?aN0Ro0l;LGtqzzL?j479hV}WwwKjx^RDQKf7~e!h z-YA2(QK_p>bk2>6SBPT%~hg6u9 zbY_BX!}LO6Iu|%89Lrs}7jSaXSWL0ezvOOn(;hRuhi*~v4SKo<2dk|PpAT0&C`C+K zD-&KIUlt%-4OwQyD%w9`Y%Ql4<_v!|iOETtBQkSfgPY{;ZlK#bO~pr0gDdhcM8hzR0rq^^`#^}TYLleW>mLWBKujWv++eTt&B z_Z=s1iqkSEFBfu{#=o&&Vq=eNAv*{ER!Hz5UQ-7W(7!ZUybrGgkR89 zA9;Zv2@$GBpEfJ+kMZCd0>4edoyfbZ+CURQD}X_DzB1xdTn~K4Q4G>R%;~)S)v9KA z8AKBFpy>#q8gST@T$?SCg0Bl2iOXd;T1c9Pv*Nd1Y=RQwss(*I@G-7N`Q->%vfRe; z&(ZN}6g-ha1(OW(uV$K9dXUy$fFF@g@lyTir!rUy7@9{6iK8ab=|pi41d=N#k->Mf zj=wn#zbhp&yIWPK$yB6iD^l?SrPAN8^C*XcCWl-!Ioi7PpcKq!Hy)p>6AbTN;2`&l z=6&kDtIcJe2ery-_Y`e^(9w6|E3I=u#GM4UI)dBAkg$x}K z+DJ1D+5#S~o(Sc6h}l%EfwDX5>k^NKSi^<^8OW8yCV=x%Kgi`VKJV4tJi~|<>8|Z3 z@xp@BDu z6w!my=n~uAVae*WZ(tPKQ@>cJJ6Ud&CUNO?@X^1@++2xY1u|s+e5O2EuA$CHYFw21 zI?gvL57t{!zKbua<2*%r3aH8bDz6tws8(J);DKbLPQ*~mjC+lMn>3Xx+`d%qc1RZk zka6TjV)bz<2NIe$yP#8|xIZE??|klDou1A=PjO>gBA(zTCA>)5l}VGCF~9-LPVVt5 z&=+ldV+EpTqT`?T){%w5m=2~1At8};mApE`!LdSIirl-~#;|FmbDs{jGSY5D5e{c0 z-=~IC%=pe{K9uJkZW#724LkDiFhLhILqQN|tT7Cp2lc_n5BSAU1`xS@gT>e8rZh*h zi-!Zvp&H+gCWwtdEbf=TjHlH6v(F1khzx+b0w$=b!v!*cMU)GDYuw<-BrZV~cvPd5 zDWBzm(9;n^j6jiHZM`pP-tZ06S<(k9F;)ZF<%hd*;loFB2hxE-K57DS(Qw{9bCMn| zbCVaxAq~?kSL5=*FBtO|+$l|Gxu^gRCmI zat`dY*?-V1(-Q?h=P&PD2LY5?E|08aG63Kuj*^TFoyrEmf{q62cg$lnP^QR9tPjW= z<$~Fa$x7LqM{$H_IXW2*kVKp)-zmbb%~TrB?PVium;ymP|Imb-G3{$COubEv30&N4 zr5gWO0h>cnTj;^U!8O7hsBY=-0CBsLnN@BMgeJ3I~nC#CkdF(m)Yy|ssz%u4D zz~vm^b$_Rf3b`-m;MSlyWTqpcAS_)`3AF(LV84+kW+S?$A&N(R6Y2-s$?JS^27`EL;d7KB_TQig#J&cGghCiu zm)sU|>0n(h!G!FYuQKDQ-)cHf>c=|4#aQVHd1##_VV@m{bfSsi3zw)*-kf40o2C)p z>jyAEe@O`saS*fj&BHi`Aps0Fi>U%pDb<$TH(xCnzh>q{78viEavKC0>uj$ZQ({); z-q=4IgguOt2}1x5V<0a!?ngrmkaKxD%qLbs?3!=YFbG6=8%QaIakKH>j7tZ7V>e03 z*Og)!4LrfVCd?Br4!{=^*2OkW>Ml`H+L5&-Z#DCPykONtvOloWp}o0$t~^l9A;fu} zLr6jJ>jgaFbLY9yq2`FGgc!MWgbPV8)NYtD8Cwh(;2%F>rB-VXc4(L{M-s0f#Q!)2 z@M9503M#1#?6poSV8JA}7o8nN9b%I>gLwE#p55;N?!lBVM>c}=1?<-~bA+yMZl)X! zC*c@SxuNi2nh+5FLbNY5B3RHUpup67PN)wWRI}p%Txz=au$9ZYU?Pbje)}jOP^Q%K zi=>|60Fccdp3a=4xi+^fvtd2V5x8CFM~}jWj`0l#n_l3lb2G#fG>I3=%j6Amphe87 zE^q^|QRFj^rAT+hy~>W-W|9s|r_AXT4))$g&O_gEH`Nq)xZ>8Y&y|0aK}r%bA+9#n zltRXF4qe?SwOFBG<1(6^6|6c)ZPv5fiI)?5Mo1=%01|`>m{`gXiKN&YIrpisQX~_> z1gsfsrD$By>|kag=WNZ?QX4TM9DsM-<3N_lv&nS{j=+kG`Q~1)BOeMA3FZGig)*SV z1iwK#COaBjsOxlceD=$i@Ow!hg06?|j%ku!a52k}R(DKz@R{ax4X< zDJTo3$ccLqmV#YnY0nKlNIbRFKX-3n6VI%%@)|xL=K!GrCXn?FUe%_NxsBPDw95M( zSiE$QR#OR`-nwm;;2U?Lqs_^G@ciKABxMaj2M~?){uuZV$hX%-5Jt;Bo(WEfVL`0v zVczSK2jWb#p>hW+GgNH*KIUY;3$D}Kn%WozM)8YYo^v~mD^EuQYnXpmg@eO_ds8w? zSPTVjlYLD&fIN`MGBA}`A@{)P9JlF9T4*={M*w&LUFnx;0T5{I1NZVk>xZH+`rA30>7VDBC0LWjx~=+70D8cMUjn}fM&i|cADA+jJSN}S<3V7+j5aJdF7-Y>s?F-6emnBs{YZjVw zQaCd?dtZqYnFsi2v|t!%!BR8~r3wtHd`fN!j%TuWkGcWJsR=c5>6IQ!ws&WwlQ|tY zr!f12&&Y^xdx)(B&}M7_p1&Oi^@qG+DoLNL2ORCMH2}M$YaYw*sQ}>*8Q+sDHRk;9%(3c&_`wHh(8PHCqGgh2%ZNJTTw>LXyN@mS6#Bd>J7=5GIfHR zgAD|iI4t>K29jR7&LG#&WS_Dx)-eW(30XZ6fC}Eq-ZxjqPq(B>W;uw}paPQHL#7{1 ziv0aPf>-5NgXM4c$_laR6btTrqH0^^XpnJXI#5pQu;>y?j&N-g%rwz|9!)Rei}f}o zC=dpn4i~v>s};iSN97;}2&B0kXh?}$S$_kGop8-{*rr1HlKteD5%-Cr4Nrwsb-Kx^ zWB`DU3m8$^lA~`=qQ8rR@$)$ng_4&=H7opjOUF z$I2FtYBj?$O^xLbGY0!)uQg=$aB8M=!%o$KK!5u#b`XkDDHFGG3ZaxFq^+f%au z(sVC$!r4*oh2-&>lswXLrJ7gk7INJ5?65{My%#cLM;pv5dJt>^&bFbICFeFGlQznI zYupsbB!Dt+kMT|>e$9aFZm^LZ0wHxfZf$OzBC})`DFu+z^1@n0Ymcmk#p1s*|By=h z+UGifX;o<=M@Kh6p-@z#cPGOzi`H8T$qUT{G;2Oc(5XzH+Ql>hG7C|UX!siqI*m9= zVI-=cK(zFhLry>$Zrq?;ktO_}YDuP>*f`7gdw|}sU!d*HReeamCdFG>c2PT+3uq8k zIrlX~8gU9^SW!U-pdCYKfCXUCPDfm4qSU(|8*t zGWW|xO(vu>+jW9L{CF^Zj+fI7kuI}802&T!WYQEv?(~y0tC`+VuybSbgQ*?}Y3gb8 zASrvrTD7bH{ou%1Hr(*+()+(BGnb>^Mub66sYu|X4vEwoMEc4%isXr6x!lR*V$uw_ zdgZEG&fVW4?TmC1@`|FSs!jzROcFv@3#R(srDU=2n_q<8m>CiH(8BlL&cPFwd0vWl z`v|vW#{ycG&*DV!0+evy*?&^6lg(n(K!n25LxpAXMeZaPPNK0{I=`#(Dp-j=f>e59 zo`xEj%6&ymd^A5Rk%QCFx{xUcxd+k)`7hD!bdv8K;XDe(tYm1E?PI?pL_u4}&@v6U zmK)10iFz$8wIc+=B``rHxSvf?-OAbR?6{ZKno+evj=D`ZMvE5yW8H2>8PFP`YL-Hm zh4etVcDenXVHgVmPJra>_F?wNhuHw4co~CzIHNcs=olFYc!7L#ErpS0sZk4mIxEu2 zmkgax96X8-jdp*f+QVz4;ymq*d(h77-%rH>+%d{dA`!c_aK9YB*$v}^#S{B0utZm> zm%XPm6KLB)$KT_=&dT1gECfrH!8}5{^$@5KajZc(G6rn{2rC#tsQ-D$+s7yKHpf3C z(~lW`3U|*afP6;ePOZcC3)G5gf^V?@O<6bGLEy8RF1|aK)FGDN5EFbZ*RxF#HQoWB zV}j+C?cZnM)srjA)TToFgEbpWgNy81yFuQ!{6&WOlU;EQFR% z+NbzDW-R99lB_~9OKat~-zVjP8JQTCK1tx$jM_i%wgR4hyU+>Tgf=);v$lw3ydmeN zL1M@R3seR!BrZuEcbN{p6l~52!!$0Knz*l8W)gColfBcWst6?c=eCl!P}Ee(4-IV? zX3q4YO45KS(9&>UmSd__!+5i`e4jTDX<+UI{uj)87&VyIMHJWC@>wowQxvxBi=kc{ zIVs`r@tG`Q`#0vb;ld%&m)(JTff|UhO`!0+wXPi&zx~>6)_CH8Nw5xuI-^UfPh)J6 zbH%cdr+id#&`0JNm=$Ezc@9uiARzhCo$^?~LlcGIj93J<9q}$~Ltx`IMb94a;Qqrw zN7`IQ8dh-!Q5Y0XwQ_~uRq1AEnvS1}rtL#-u)(bGk}Vfws{R>KifOt27;pEAecbv8 z(nDybWM3r+VooP!Fy{cu2RJ-v*g&u%Ot;-6uh`|#?2=h-?KI$|5MSJJm!We3kFI9= z8^Wg>ua{-jRt~{FIH|ncX}3gD-`4LFv9-P6dhF<){$sQj9%l;UNE{GXm)+2Im5DOm#v8AW4MI2~dF*rb#AYS-D((NL`MGf=*s`JL5z=(7wtABW>IvT-G&ql(S)( zGPZB9(@!CYqC|!$kh9zHF&m`a5S3&rFY z**}U&d0M93`>2jK{emn}1U6^K=v?5y$>inRp^^q!V}4|j1Avqx4E+SB{k=t`aN^&h zwWN)sYWsSO%pKZ*XNdqBYEwP0QR$QnDBsv(ZtX~QZb)v;tj%pol@wV@%+Uj!1J-3n zTh9DrQLepfT~|+6Pe<;*{%CM2)3t7M`_^37nI9X(Z&xw-bOUAcAb z&XND^Xa4&$K6E+HIH){w`cQgKYipIHT+hCb4FKgQwMl6g#;lr9F2@{ zhS26Vg-}G2%y^F=f*a<*Vy5Cs3L}9;rbN6MMlf`IK;~(J4*v6Tz(dh|A_B+UkH(XK zq4+~GN)83G9cIsyDMd?%$`*JAJ-e#!u}l-fgjpIG{>)cd`qs~jSS^-vmvbziSbeG^ z#p10$y^ia*ZrNDRg5J`d>#FbEd|v&gTo=~Hu9Wl719nvFBCeiLy|t^eJJVHNSIwH- znB7`EVfl{g_S7c*t94oa_@6(vu6+&v-rl`&V=BFozvmZob=Cj%nAH<@?A%%Re}DI% z8|8oh82_@f{`U`}Tk)U2-L-C0ZfjR+Q^)`Oo&3Ky@IN1x5AlES>8#voaVGxr`xv*W z(|vBPb1U1)9jK4g`^ROnYa{i6P<_blO?R)`n00%0oc{kg-J8<6&Yfc|w^=Qgc}?d8 F{tt!hdt?9r literal 0 HcmV?d00001 diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/similarity_groups/._SUCCESS.crc b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/similarity_groups/._SUCCESS.crc new file mode 100644 index 0000000000000000000000000000000000000000..3b7b044936a890cd8d651d349a752d819d71d22c GIT binary patch literal 8 PcmYc;N@ieSU}69O2$TUk literal 0 HcmV?d00001 diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/similarity_groups/.part-00000-ad5faba8-5922-42f1-a215-1619e7bb4e5d-c000.snappy.parquet.crc b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/assertions/similarity_groups/.part-00000-ad5faba8-5922-42f1-a215-1619e7bb4e5d-c000.snappy.parquet.crc new file mode 100644 index 0000000000000000000000000000000000000000..9ac084585a97ad79c918cf9170380da4cbfd5c76 GIT binary patch literal 3904 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 O#tibKGZ)AyUu_J3V} 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

5GN1rVO1z8==@mCD}u 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 literal 0 HcmV?d00001 From 4cf79f32eb2b0de4afd14ade83b43abf0d5fdef0 Mon Sep 17 00:00:00 2001 From: miconis Date: Fri, 25 Sep 2020 11:29:51 +0200 Subject: [PATCH 004/445] implementation of the oozie wf to prepare the openorgs input: relations between organizations --- .../eu/dnetlib/dhp/oa/dedup/OrgSimRel.java | 83 +++++++ .../dhp/oa/dedup/SparkCollectSimRels.java | 21 +- .../dhp/oa/dedup/SparkPrepareOrgRels.java | 168 ++++++++++++++ .../orgsdedup/oozie_app/config-default.xml | 18 ++ .../oa/dedup/orgsdedup/oozie_app/workflow.xml | 212 ++++++++++++++++++ .../oa/dedup/prepareOrgRels_parameters.json | 56 +++++ 6 files changed, 552 insertions(+), 6 deletions(-) create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/OrgSimRel.java create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/config-default.xml create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/workflow.xml create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/OrgSimRel.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/OrgSimRel.java new file mode 100644 index 000000000..a7d8ead0b --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/OrgSimRel.java @@ -0,0 +1,83 @@ +package eu.dnetlib.dhp.oa.dedup; + +import java.io.Serializable; + +public class OrgSimRel implements Serializable { + + String local_id; + String oa_original_id; + String oa_name; + String oa_acronym; + String oa_country; + String oa_url; + String oa_collectedfrom; + + public OrgSimRel() { + } + + public OrgSimRel(String local_id, String oa_original_id, String oa_name, String oa_acronym, String oa_country, String oa_url, String oa_collectedfrom) { + this.local_id = local_id; + this.oa_original_id = oa_original_id; + this.oa_name = oa_name; + this.oa_acronym = oa_acronym; + this.oa_country = oa_country; + this.oa_url = oa_url; + this.oa_collectedfrom = oa_collectedfrom; + } + + public String getLocal_id() { + return local_id; + } + + public void setLocal_id(String local_id) { + this.local_id = local_id; + } + + public String getOa_original_id() { + return oa_original_id; + } + + public void setOa_original_id(String oa_original_id) { + this.oa_original_id = oa_original_id; + } + + public String getOa_name() { + return oa_name; + } + + public void setOa_name(String oa_name) { + this.oa_name = oa_name; + } + + public String getOa_acronym() { + return oa_acronym; + } + + public void setOa_acronym(String oa_acronym) { + this.oa_acronym = oa_acronym; + } + + public String getOa_country() { + return oa_country; + } + + public void setOa_country(String oa_country) { + this.oa_country = oa_country; + } + + public String getOa_url() { + return oa_url; + } + + public void setOa_url(String oa_url) { + this.oa_url = oa_url; + } + + public String getOa_collectedfrom() { + return oa_collectedfrom; + } + + public void setOa_collectedfrom(String oa_collectedfrom) { + this.oa_collectedfrom = oa_collectedfrom; + } +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCollectSimRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCollectSimRels.java index 7c1e6550e..b35f9c54a 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCollectSimRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCollectSimRels.java @@ -4,16 +4,20 @@ import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.schema.oaf.DataInfo; import eu.dnetlib.dhp.schema.oaf.Relation; import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import eu.dnetlib.pace.config.DedupConfig; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaPairRDD; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.sql.*; +import org.dom4j.DocumentException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import scala.Tuple2; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -65,7 +69,7 @@ public class SparkCollectSimRels extends AbstractSparkAction { } @Override - void run(ISLookUpService isLookUpService) { + void run(ISLookUpService isLookUpService) throws DocumentException, ISLookUpException, IOException { // read oozie parameters final String isLookUpUrl = parser.get("isLookUpUrl"); @@ -126,11 +130,16 @@ public class SparkCollectSimRels extends AbstractSparkAction { Encoders.bean(Relation.class) ).repartition(numPartitions); - savePostgresRelation(organizationRelations, workingPath, actionSetId, "organization"); - savePostgresRelation(resultRelations, workingPath, actionSetId, "publication"); - savePostgresRelation(resultRelations, workingPath, actionSetId, "software"); - savePostgresRelation(resultRelations, workingPath, actionSetId, "otherresearchproduct"); - savePostgresRelation(resultRelations, workingPath, actionSetId, "dataset"); + for (DedupConfig dedupConf : getConfigurations(isLookUpService, actionSetId)) { + switch(dedupConf.getWf().getSubEntityValue()){ + case "organization": + savePostgresRelation(organizationRelations, workingPath, actionSetId, "organization"); + break; + default: + savePostgresRelation(resultRelations, workingPath, actionSetId, dedupConf.getWf().getSubEntityValue()); + break; + } + } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java new file mode 100644 index 000000000..dff1bbb0d --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java @@ -0,0 +1,168 @@ +package eu.dnetlib.dhp.oa.dedup; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.common.EntityType; +import eu.dnetlib.dhp.schema.common.ModelSupport; +import eu.dnetlib.dhp.schema.oaf.*; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import eu.dnetlib.pace.config.DedupConfig; +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaRDD; +import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.api.java.function.MapGroupsFunction; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.SaveMode; +import org.apache.spark.sql.SparkSession; +import org.dom4j.DocumentException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import scala.Tuple2; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; + +public class SparkPrepareOrgRels extends AbstractSparkAction { + + private static final Logger log = LoggerFactory.getLogger(SparkCreateDedupRecord.class); + + public static final String ROOT_TRUST = "0.8"; + public static final String PROVENANCE_ACTION_CLASS = "sysimport:dedup"; + public static final String PROVENANCE_ACTIONS = "dnet:provenanceActions"; + + public SparkPrepareOrgRels(ArgumentApplicationParser parser, SparkSession spark) { + super(parser, spark); + } + + public static void main(String[] args) throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCreateSimRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json"))); + parser.parseArgument(args); + + SparkConf conf = new SparkConf(); + conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer"); + conf.registerKryoClasses(ModelSupport.getOafModelClasses()); + + new SparkCreateDedupRecord(parser, getSparkSession(conf)) + .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); + } + + @Override + public void run(ISLookUpService isLookUpService) throws IOException { + + final String graphBasePath = parser.get("graphBasePath"); + final String isLookUpUrl = parser.get("isLookUpUrl"); + final String actionSetId = parser.get("actionSetId"); + final String workingPath = parser.get("workingPath"); + final String apiUrl = parser.get("apiUrl"); + final String dbUrl = parser.get("dbUrl"); + final String dbTable = parser.get("dbTable"); + final String dbUser = parser.get("dbUser"); + final String dbPwd = parser.get("dbPwd"); + + log.info("graphBasePath: '{}'", graphBasePath); + log.info("isLookUpUrl: '{}'", isLookUpUrl); + log.info("actionSetId: '{}'", actionSetId); + log.info("workingPath: '{}'", workingPath); + log.info("apiUrl: '{}'", apiUrl); + log.info("dbUrl: '{}'", dbUrl); + log.info("dbUser: '{}'", dbUser); + log.info("table: '{}'", dbTable); + log.info("dbPwd: '{}'", "xxx"); + + final String mergeRelPath = DedupUtility.createMergeRelPath(workingPath, actionSetId, "organization"); + final String entityPath = DedupUtility.createEntityPath(graphBasePath, "organization"); + + Dataset relations = createRelations(spark, mergeRelPath, entityPath); + + final Properties connectionProperties = new Properties(); + connectionProperties.put("user", dbUser); + connectionProperties.put("password", dbPwd); + + relations.write().mode(SaveMode.Overwrite).jdbc(dbUrl, dbTable, connectionProperties); + + if (!apiUrl.isEmpty()) + updateSimRels(apiUrl); + + } + + public static Dataset createRelations( + final SparkSession spark, + final String mergeRelsPath, + final String entitiesPath) { + + // + Dataset> entities = spark + .read() + .textFile(entitiesPath) + .map( + (MapFunction>) it -> { + Organization entity = OBJECT_MAPPER.readValue(it, Organization.class); + return new Tuple2<>(entity.getId(), entity); + }, + Encoders.tuple(Encoders.STRING(), Encoders.kryo(Organization.class))); + + Dataset> relations = spark.createDataset( + spark + .read() + .load(mergeRelsPath) + .as(Encoders.bean(Relation.class)) + .where("relClass == 'merges'") + .toJavaRDD() + .mapToPair(r -> new Tuple2<>(r.getSource(), r.getTarget())) + .groupByKey() + .flatMap(g -> { + List> rels = new ArrayList<>(); + for (String id1 : g._2()) { + for (String id2 : g._2()) { + if (!id1.equals(id2)) + if (id1.contains("openorgs")) + rels.add(new Tuple2<>(id1, id2)); + } + } + return rels.iterator(); + }).rdd(), + Encoders.tuple(Encoders.STRING(), Encoders.STRING())); + + return relations + .joinWith(entities, relations.col("_2").equalTo(entities.col("_1")), "inner") + .map( + (MapFunction, Tuple2>, OrgSimRel>)r -> + new OrgSimRel( + r._1()._2(), + r._2()._2().getOriginalId().get(0), + r._2()._2().getLegalname().getValue(), + r._2()._2().getLegalshortname().getValue(), + r._2()._2().getCountry().getClassid(), + r._2()._2().getWebsiteurl().getValue(), + r._2()._2().getCollectedfrom().get(0).getValue() + ), + Encoders.bean(OrgSimRel.class) + ); + + } + + private static String updateSimRels(final String apiUrl) throws IOException { + final HttpGet req = new HttpGet(apiUrl); + try (final CloseableHttpClient client = HttpClients.createDefault()) { + try (final CloseableHttpResponse response = client.execute(req)) { + return IOUtils.toString(response.getEntity().getContent()); + } + } + } + +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/config-default.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/config-default.xml new file mode 100644 index 000000000..2e0ed9aee --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/config-default.xml @@ -0,0 +1,18 @@ + + + jobTracker + yarnRM + + + nameNode + hdfs://nameservice1 + + + oozie.use.system.libpath + true + + + oozie.action.sharelib.for.spark + spark2 + + \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/workflow.xml new file mode 100644 index 000000000..82ecbc46b --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/workflow.xml @@ -0,0 +1,212 @@ + + + + graphBasePath + the raw graph base path + + + isLookUpUrl + the address of the lookUp service + + + actionSetId + id of the actionSet + + + workingPath + path for the working directory + + + dedupGraphPath + path for the output graph + + + cutConnectedComponent + max number of elements in a connected component + + + apiUrl + the url for the APIs of the openorgs service + + + dbUrl + the url of the database + + + dbUser + the user of the database + + + dbTable + the name of the table in the database + + + dbPwd + the passowrd of the user of the database + + + sparkDriverMemory + memory for driver process + + + sparkExecutorMemory + memory for individual executor + + + sparkExecutorCores + number of cores used by single executor + + + oozieActionShareLibForSpark2 + oozie action sharelib for spark 2.* + + + spark2ExtraListeners + com.cloudera.spark.lineage.NavigatorAppListener + spark 2.* extra listeners classname + + + spark2SqlQueryExecutionListeners + com.cloudera.spark.lineage.NavigatorQueryListener + spark 2.* sql query execution listeners classname + + + spark2YarnHistoryServerAddress + spark 2.* yarn history server address + + + spark2EventLogDir + spark 2.* event log dir location + + + + + ${jobTracker} + ${nameNode} + + + mapreduce.job.queuename + ${queueName} + + + oozie.launcher.mapred.job.queue.name + ${oozieLauncherQueueName} + + + oozie.action.sharelib.for.spark + ${oozieActionShareLibForSpark2} + + + + + + + + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] + + + + + + + + + + + + + yarn + cluster + Create Similarity Relations + eu.dnetlib.dhp.oa.dedup.SparkCreateSimRels + dhp-dedup-openaire-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=3840 + + --graphBasePath${graphBasePath} + --isLookUpUrl${isLookUpUrl} + --actionSetId${actionSetId} + --workingPath${workingPath} + --numPartitions8000 + + + + + + + + -pb + ${graphBasePath}/relation + ${workingPath}/organization_simrel + + + + + + + + yarn + cluster + Create Merge Relations + eu.dnetlib.dhp.oa.dedup.SparkCreateMergeRels + dhp-dedup-openaire-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=3840 + + --graphBasePath${graphBasePath} + --workingPath${workingPath} + --isLookUpUrl${isLookUpUrl} + --actionSetId${actionSetId} + --cutConnectedComponent${cutConnectedComponent} + + + + + + + + yarn + cluster + Prepare Organization Relations + eu.dnetlib.dhp.oa.dedup.SparkPrepareOrgRels + dhp-dedup-openaire-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=3840 + + --graphBasePath${graphBasePath} + --workingPath${workingPath} + --isLookUpUrl${isLookUpUrl} + --actionSetId${actionSetId} + --apiUrl${apiUrl} + --dbUrl${dbUrl} + --dbTable${dbTable} + --dbUser${dbUser} + --dbPwd${dbPwd} + + + + + + + \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json new file mode 100644 index 000000000..bcca48a15 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json @@ -0,0 +1,56 @@ +[ + { + "paramName": "i", + "paramLongName": "graphBasePath", + "paramDescription": "the base path of raw graph", + "paramRequired": true + }, + { + "paramName": "w", + "paramLongName": "workingPath", + "paramDescription": "the working directory path", + "paramRequired": true + }, + { + "paramName": "la", + "paramLongName": "isLookUpUrl", + "paramDescription": "the url of the lookup service", + "paramRequired": true + }, + { + "paramName": "asi", + "paramLongName": "actionSetId", + "paramDescription": "the id of the actionset (orchestrator)", + "paramRequired": true + }, + { + "paramName": "au", + "paramLongName": "apiUrl", + "paramDescription": "the url for the APIs of the openorgs service", + "paramRequired": true + }, + { + "paramName": "du", + "paramLongName": "dbUrl", + "paramDescription": "the url of the database", + "paramRequired": true + }, + { + "paramName": "dusr", + "paramLongName": "dbUser", + "paramDescription": "the user of the database", + "paramRequired": true + }, + { + "paramName": "t", + "paramLongName": "dbTable", + "paramDescription": "the name of the table in the database", + "paramRequired": true + }, + { + "paramName": "dpwd", + "paramLongName": "dbPwd", + "paramDescription": "the password for the user of the database", + "paramRequired": true + } +] \ No newline at end of file From e3f7798d1bb7ee367cba0ed25d613c05943a880f Mon Sep 17 00:00:00 2001 From: miconis Date: Tue, 29 Sep 2020 15:31:46 +0200 Subject: [PATCH 005/445] minor changes in dedup tests, bug fix in the idgenerator and pace-core version update --- dhp-workflows/dhp-dedup-openaire/pom.xml | 4 + .../dhp/oa/dedup/AbstractSparkAction.java | 1 + .../dhp/oa/dedup/DedupRecordFactory.java | 27 +- .../eu/dnetlib/dhp/oa/dedup/IdGenerator.java | 146 +++++---- .../eu/dnetlib/dhp/oa/dedup/Identifier.java | 208 +++++++------ .../eu/dnetlib/dhp/oa/dedup/OrgSimRel.java | 135 ++++---- .../java/eu/dnetlib/dhp/oa/dedup/PidType.java | 30 +- .../dhp/oa/dedup/SparkCollectSimRels.java | 288 +++++++++--------- .../dhp/oa/dedup/SparkCreateMergeRels.java | 14 +- .../dhp/oa/dedup/SparkCreateSimRels.java | 22 +- .../dhp/oa/dedup/SparkPrepareOrgRels.java | 274 +++++++++-------- .../oa/dedup/orgsdedup/oozie_app/workflow.xml | 27 +- .../oa/dedup/prepareOrgRels_parameters.json | 8 +- .../dhp/oa/dedup/EntityMergerTest.java | 8 +- .../dnetlib/dhp/oa/dedup/SparkDedupTest.java | 226 +++++++------- pom.xml | 2 +- 16 files changed, 751 insertions(+), 669 deletions(-) diff --git a/dhp-workflows/dhp-dedup-openaire/pom.xml b/dhp-workflows/dhp-dedup-openaire/pom.xml index 03ddbcf4c..ff11c66e0 100644 --- a/dhp-workflows/dhp-dedup-openaire/pom.xml +++ b/dhp-workflows/dhp-dedup-openaire/pom.xml @@ -90,6 +90,10 @@ com.fasterxml.jackson.core jackson-core + + org.apache.httpcomponents + httpclient + diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java index 74cecb7b6..9a1127764 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java @@ -29,6 +29,7 @@ import eu.dnetlib.pace.config.DedupConfig; abstract class AbstractSparkAction implements Serializable { protected static final int NUM_PARTITIONS = 1000; + protected static final int NUM_CONNECTIONS = 20; protected static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java index 0fc393ea5..50dda887b 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java @@ -1,12 +1,10 @@ package eu.dnetlib.dhp.oa.dedup; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.Lists; -import eu.dnetlib.dhp.schema.common.EntityType; -import eu.dnetlib.dhp.schema.common.ModelSupport; -import eu.dnetlib.dhp.schema.oaf.*; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + import org.apache.commons.lang.StringUtils; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.api.java.function.MapGroupsFunction; @@ -15,11 +13,15 @@ import org.apache.spark.sql.Encoders; import org.apache.spark.sql.SparkSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import scala.Tuple2; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.*; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Lists; + +import eu.dnetlib.dhp.schema.common.EntityType; +import eu.dnetlib.dhp.schema.common.ModelSupport; +import eu.dnetlib.dhp.schema.oaf.*; +import scala.Tuple2; public class DedupRecordFactory { @@ -80,14 +82,14 @@ public class DedupRecordFactory { final Collection dates = Lists.newArrayList(); final List> authors = Lists.newArrayList(); - final List bestPids = Lists.newArrayList(); //best pids list + final List bestPids = Lists.newArrayList(); // best pids list entities .forEachRemaining( t -> { T duplicate = t._2(); - //prepare the list of pids to use for the id generation + // prepare the list of pids to use for the id generation bestPids.addAll(IdGenerator.bestPidtoIdentifier(duplicate)); entity.mergeFrom(duplicate); @@ -115,5 +117,4 @@ public class DedupRecordFactory { return entity; } - } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java index 2d203a1b1..2916e063d 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java @@ -1,90 +1,112 @@ -package eu.dnetlib.dhp.oa.dedup; -import com.google.common.collect.Lists; -import eu.dnetlib.dhp.schema.common.EntityType; -import eu.dnetlib.dhp.schema.common.ModelSupport; -import eu.dnetlib.dhp.schema.oaf.Field; -import eu.dnetlib.dhp.schema.oaf.OafEntity; -import eu.dnetlib.dhp.schema.oaf.Result; -import eu.dnetlib.dhp.schema.oaf.StructuredProperty; -import org.apache.commons.lang.NullArgumentException; -import org.apache.commons.lang.StringUtils; +package eu.dnetlib.dhp.oa.dedup; import java.io.Serializable; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; +import org.apache.commons.lang.NullArgumentException; +import org.apache.commons.lang.StringUtils; + +import com.google.common.collect.Lists; + +import eu.dnetlib.dhp.schema.common.EntityType; +import eu.dnetlib.dhp.schema.common.ModelSupport; +import eu.dnetlib.dhp.schema.oaf.Field; +import eu.dnetlib.dhp.schema.oaf.OafEntity; +import eu.dnetlib.dhp.schema.oaf.Result; +import eu.dnetlib.dhp.schema.oaf.StructuredProperty; + public class IdGenerator implements Serializable { - private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - public static String CROSSREF_ID = "10|openaire____::081b82f96300b6a6e3d282bad31cb6e2"; - public static String DATACITE_ID = "10|openaire____::9e3be59865b2c1c335d32dae2fe7b254"; + public static String CROSSREF_ID = "10|openaire____::081b82f96300b6a6e3d282bad31cb6e2"; + public static String DATACITE_ID = "10|openaire____::9e3be59865b2c1c335d32dae2fe7b254"; - //pick the best pid from the list (consider date and pidtype) - public static String generate(List pids, String defaultID) { - if (pids == null || pids.size() == 0) - return defaultID; + // pick the best pid from the list (consider date and pidtype) + public static String generate(List pids, String defaultID) { + if (pids == null || pids.size() == 0) + return defaultID; - Optional bp = pids.stream() - .max(Identifier::compareTo); + Optional bp = pids + .stream() + .max(Identifier::compareTo); - if (bp.get().isUseOriginal() || bp.get().getPid().getValue() == null) { - return bp.get().getOriginalID().split("\\|")[0] + "|dedup_wf_001::" + DedupUtility.md5(bp.get().getOriginalID()); - } else { - return bp.get().getOriginalID().split("\\|")[0] + "|" + createPrefix(bp.get().getPid().getQualifier().getClassid()) + "::" + DedupUtility.md5(bp.get().getPid().getValue()); - } + if (bp.get().isUseOriginal() || bp.get().getPid().getValue() == null) { + return bp.get().getOriginalID().split("\\|")[0] + "|dedup_wf_001::" + + DedupUtility.md5(bp.get().getOriginalID()); + } else { + return bp.get().getOriginalID().split("\\|")[0] + "|" + + createPrefix(bp.get().getPid().getQualifier().getClassid()) + "::" + + DedupUtility.md5(bp.get().getPid().getValue()); + } - } + } - //pick the best pid from the entity. Returns a list (length 1) to save time in the call - public static List bestPidtoIdentifier(T entity) { + // pick the best pid from the entity. Returns a list (length 1) to save time in the call + public static List bestPidtoIdentifier(T entity) { - if (entity.getPid() == null || entity.getPid().size() == 0) - return Lists.newArrayList(new Identifier(new StructuredProperty(), new Date(), PidType.original, entity.getCollectedfrom(), EntityType.fromClass(entity.getClass()), entity.getId())); + if (entity.getPid() == null || entity.getPid().size() == 0) + return Lists + .newArrayList( + new Identifier(new StructuredProperty(), new Date(), PidType.original, entity.getCollectedfrom(), + EntityType.fromClass(entity.getClass()), entity.getId())); - Optional bp = entity.getPid().stream() - .filter(pid -> PidType.classidValueOf(pid.getQualifier().getClassid()) != PidType.undefined) - .max(Comparator.comparing(pid -> PidType.classidValueOf(pid.getQualifier().getClassid()))); + Optional bp = entity + .getPid() + .stream() + .filter(pid -> PidType.classidValueOf(pid.getQualifier().getClassid()) != PidType.undefined) + .max(Comparator.comparing(pid -> PidType.classidValueOf(pid.getQualifier().getClassid()))); - return bp.map(structuredProperty -> - Lists.newArrayList(new Identifier(structuredProperty, extractDate(entity, sdf), PidType.classidValueOf(structuredProperty.getQualifier().getClassid()), entity.getCollectedfrom(), EntityType.fromClass(entity.getClass()), entity.getId())) - ).orElseGet(() -> Lists.newArrayList(new Identifier(new StructuredProperty(), new Date(), PidType.original, entity.getCollectedfrom(), EntityType.fromClass(entity.getClass()), entity.getId()))); + return bp + .map( + structuredProperty -> Lists + .newArrayList( + new Identifier(structuredProperty, extractDate(entity, new SimpleDateFormat("yyyy-MM-dd")), + PidType.classidValueOf(structuredProperty.getQualifier().getClassid()), + entity.getCollectedfrom(), EntityType.fromClass(entity.getClass()), entity.getId()))) + .orElseGet( + () -> Lists + .newArrayList( + new Identifier(new StructuredProperty(), new Date(), PidType.original, + entity.getCollectedfrom(), EntityType.fromClass(entity.getClass()), entity.getId()))); - } + } - //create the prefix (length = 12): dedup_+ pidType - public static String createPrefix(String pidType) { + // create the prefix (length = 12): dedup_+ pidType + public static String createPrefix(String pidType) { - StringBuilder prefix = new StringBuilder("dedup_" + pidType); + StringBuilder prefix = new StringBuilder("dedup_" + pidType); - while (prefix.length() < 12) { - prefix.append("_"); - } - return prefix.toString().substring(0, 12); + while (prefix.length() < 12) { + prefix.append("_"); + } + return prefix.toString().substring(0, 12); - } + } - //extracts the date from the record. If the date is not available or is not wellformed, it returns a base date: 00-01-01 - public static Date extractDate(T duplicate, SimpleDateFormat sdf){ + // extracts the date from the record. If the date is not available or is not wellformed, it returns a base date: + // 00-01-01 + public static Date extractDate(T duplicate, SimpleDateFormat sdf) { - String date = "2000-01-01"; - if (ModelSupport.isSubClass(duplicate, Result.class)) { - Result result = (Result) duplicate; - if (isWellformed(result.getDateofacceptance())){ - date = result.getDateofacceptance().getValue(); - } - } + String date = "2000-01-01"; + if (ModelSupport.isSubClass(duplicate, Result.class)) { + Result result = (Result) duplicate; + if (isWellformed(result.getDateofacceptance())) { + date = result.getDateofacceptance().getValue(); + } + } - try { - return sdf.parse(date); - } catch (ParseException e) { - return new Date(); - } + try { + return sdf.parse(date); + } catch (ParseException e) { + return new Date(); + } - } + } - public static boolean isWellformed(Field date) { - return date != null && StringUtils.isNotBlank(date.getValue()) && date.getValue().matches(DatePicker.DATE_PATTERN) && DatePicker.inRange(date.getValue()); - } + public static boolean isWellformed(Field date) { + return date != null && StringUtils.isNotBlank(date.getValue()) + && date.getValue().matches(DatePicker.DATE_PATTERN) && DatePicker.inRange(date.getValue()); + } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/Identifier.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/Identifier.java index fd52d20f9..480b52341 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/Identifier.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/Identifier.java @@ -1,132 +1,138 @@ -package eu.dnetlib.dhp.oa.dedup; -import eu.dnetlib.dhp.schema.common.EntityType; -import eu.dnetlib.dhp.schema.oaf.KeyValue; -import eu.dnetlib.dhp.schema.oaf.StructuredProperty; +package eu.dnetlib.dhp.oa.dedup; import java.io.Serializable; import java.util.Date; import java.util.List; -public class Identifier implements Serializable, Comparable{ +import eu.dnetlib.dhp.schema.common.EntityType; +import eu.dnetlib.dhp.schema.oaf.KeyValue; +import eu.dnetlib.dhp.schema.oaf.StructuredProperty; - StructuredProperty pid; - Date date; - PidType type; - List collectedFrom; - EntityType entityType; - String originalID; +public class Identifier implements Serializable, Comparable { - boolean useOriginal = false; //to know if the top identifier won because of the alphabetical order of the original ID + StructuredProperty pid; + Date date; + PidType type; + List collectedFrom; + EntityType entityType; + String originalID; - public Identifier(StructuredProperty pid, Date date, PidType type, List collectedFrom, EntityType entityType, String originalID) { - this.pid = pid; - this.date = date; - this.type = type; - this.collectedFrom = collectedFrom; - this.entityType = entityType; - this.originalID = originalID; - } + boolean useOriginal = false; // to know if the top identifier won because of the alphabetical order of the original + // ID - public StructuredProperty getPid() { - return pid; - } + public Identifier(StructuredProperty pid, Date date, PidType type, List collectedFrom, + EntityType entityType, String originalID) { + this.pid = pid; + this.date = date; + this.type = type; + this.collectedFrom = collectedFrom; + this.entityType = entityType; + this.originalID = originalID; + } - public void setPid(StructuredProperty pidValue) { - this.pid = pid; - } + public StructuredProperty getPid() { + return pid; + } - public Date getDate() { - return date; - } + public void setPid(StructuredProperty pidValue) { + this.pid = pid; + } - public void setDate(Date date) { - this.date = date; - } + public Date getDate() { + return date; + } - public PidType getType() { - return type; - } + public void setDate(Date date) { + this.date = date; + } - public void setType(PidType type) { - this.type = type; - } + public PidType getType() { + return type; + } - public List getCollectedFrom() { - return collectedFrom; - } + public void setType(PidType type) { + this.type = type; + } - public void setCollectedFrom(List collectedFrom) { - this.collectedFrom = collectedFrom; - } + public List getCollectedFrom() { + return collectedFrom; + } - public EntityType getEntityType() { - return entityType; - } + public void setCollectedFrom(List collectedFrom) { + this.collectedFrom = collectedFrom; + } - public void setEntityType(EntityType entityType) { - this.entityType = entityType; - } + public EntityType getEntityType() { + return entityType; + } - public String getOriginalID() { - return originalID; - } + public void setEntityType(EntityType entityType) { + this.entityType = entityType; + } - public void setOriginalID(String originalID) { - this.originalID = originalID; - } + public String getOriginalID() { + return originalID; + } - public boolean isUseOriginal() { - return useOriginal; - } + public void setOriginalID(String originalID) { + this.originalID = originalID; + } - public void setUseOriginal(boolean useOriginal) { - this.useOriginal = useOriginal; - } + public boolean isUseOriginal() { + return useOriginal; + } - @Override - public int compareTo(Identifier i) { - //priority in comparisons: 1) pidtype, 2) collectedfrom (depending on the entity type) , 3) date 4) alphabetical order of the originalID - if (this.getType().compareTo(i.getType()) == 0){ //same type - if (entityType == EntityType.publication) { - if (isFromDatasourceID(this.collectedFrom, IdGenerator.CROSSREF_ID) && !isFromDatasourceID(i.collectedFrom, IdGenerator.CROSSREF_ID)) - return 1; - if (isFromDatasourceID(i.collectedFrom, IdGenerator.CROSSREF_ID) && !isFromDatasourceID(this.collectedFrom, IdGenerator.CROSSREF_ID)) - return -1; - } - if (entityType == EntityType.dataset) { - if (isFromDatasourceID(this.collectedFrom, IdGenerator.DATACITE_ID) && !isFromDatasourceID(i.collectedFrom, IdGenerator.DATACITE_ID)) - return 1; - if (isFromDatasourceID(i.collectedFrom, IdGenerator.DATACITE_ID) && !isFromDatasourceID(this.collectedFrom, IdGenerator.DATACITE_ID)) - return -1; - } + public void setUseOriginal(boolean useOriginal) { + this.useOriginal = useOriginal; + } - if (this.getDate().compareTo(date) == 0) {//same date + @Override + public int compareTo(Identifier i) { + // priority in comparisons: 1) pidtype, 2) collectedfrom (depending on the entity type) , 3) date 4) + // alphabetical order of the originalID + if (this.getType().compareTo(i.getType()) == 0) { // same type + if (entityType == EntityType.publication) { + if (isFromDatasourceID(this.collectedFrom, IdGenerator.CROSSREF_ID) + && !isFromDatasourceID(i.collectedFrom, IdGenerator.CROSSREF_ID)) + return 1; + if (isFromDatasourceID(i.collectedFrom, IdGenerator.CROSSREF_ID) + && !isFromDatasourceID(this.collectedFrom, IdGenerator.CROSSREF_ID)) + return -1; + } + if (entityType == EntityType.dataset) { + if (isFromDatasourceID(this.collectedFrom, IdGenerator.DATACITE_ID) + && !isFromDatasourceID(i.collectedFrom, IdGenerator.DATACITE_ID)) + return 1; + if (isFromDatasourceID(i.collectedFrom, IdGenerator.DATACITE_ID) + && !isFromDatasourceID(this.collectedFrom, IdGenerator.DATACITE_ID)) + return -1; + } - if (this.originalID.compareTo(i.originalID) > 0) - this.useOriginal = true; - else - i.setUseOriginal(true); + if (this.getDate().compareTo(date) == 0) {// same date - //the minus because we need to take the alphabetically lower id - return -this.originalID.compareTo(i.originalID); - } - else - //the minus is because we need to take the elder date - return -this.getDate().compareTo(date); - } - else { - return this.getType().compareTo(i.getType()); - } + if (this.originalID.compareTo(i.originalID) > 0) + this.useOriginal = true; + else + i.setUseOriginal(true); - } + // the minus because we need to take the alphabetically lower id + return -this.originalID.compareTo(i.originalID); + } else + // the minus is because we need to take the elder date + return -this.getDate().compareTo(date); + } else { + return this.getType().compareTo(i.getType()); + } - public boolean isFromDatasourceID(List collectedFrom, String dsId){ + } - for(KeyValue cf: collectedFrom) { - if(cf.getKey().equals(dsId)) - return true; - } - return false; - } + public boolean isFromDatasourceID(List collectedFrom, String dsId) { + + for (KeyValue cf : collectedFrom) { + if (cf.getKey().equals(dsId)) + return true; + } + return false; + } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/OrgSimRel.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/OrgSimRel.java index a7d8ead0b..84dfecd62 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/OrgSimRel.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/OrgSimRel.java @@ -1,83 +1,98 @@ + package eu.dnetlib.dhp.oa.dedup; import java.io.Serializable; public class OrgSimRel implements Serializable { - String local_id; - String oa_original_id; - String oa_name; - String oa_acronym; - String oa_country; - String oa_url; - String oa_collectedfrom; + String local_id; + String oa_original_id; + String oa_name; + String oa_acronym; + String oa_country; + String oa_url; + String oa_collectedfrom; - public OrgSimRel() { - } + public OrgSimRel() { + } - public OrgSimRel(String local_id, String oa_original_id, String oa_name, String oa_acronym, String oa_country, String oa_url, String oa_collectedfrom) { - this.local_id = local_id; - this.oa_original_id = oa_original_id; - this.oa_name = oa_name; - this.oa_acronym = oa_acronym; - this.oa_country = oa_country; - this.oa_url = oa_url; - this.oa_collectedfrom = oa_collectedfrom; - } + public OrgSimRel(String local_id, String oa_original_id, String oa_name, String oa_acronym, String oa_country, + String oa_url, String oa_collectedfrom) { + this.local_id = local_id; + this.oa_original_id = oa_original_id; + this.oa_name = oa_name; + this.oa_acronym = oa_acronym; + this.oa_country = oa_country; + this.oa_url = oa_url; + this.oa_collectedfrom = oa_collectedfrom; + } - public String getLocal_id() { - return local_id; - } + public String getLocal_id() { + return local_id; + } - public void setLocal_id(String local_id) { - this.local_id = local_id; - } + public void setLocal_id(String local_id) { + this.local_id = local_id; + } - public String getOa_original_id() { - return oa_original_id; - } + public String getOa_original_id() { + return oa_original_id; + } - public void setOa_original_id(String oa_original_id) { - this.oa_original_id = oa_original_id; - } + public void setOa_original_id(String oa_original_id) { + this.oa_original_id = oa_original_id; + } - public String getOa_name() { - return oa_name; - } + public String getOa_name() { + return oa_name; + } - public void setOa_name(String oa_name) { - this.oa_name = oa_name; - } + public void setOa_name(String oa_name) { + this.oa_name = oa_name; + } - public String getOa_acronym() { - return oa_acronym; - } + public String getOa_acronym() { + return oa_acronym; + } - public void setOa_acronym(String oa_acronym) { - this.oa_acronym = oa_acronym; - } + public void setOa_acronym(String oa_acronym) { + this.oa_acronym = oa_acronym; + } - public String getOa_country() { - return oa_country; - } + public String getOa_country() { + return oa_country; + } - public void setOa_country(String oa_country) { - this.oa_country = oa_country; - } + public void setOa_country(String oa_country) { + this.oa_country = oa_country; + } - public String getOa_url() { - return oa_url; - } + public String getOa_url() { + return oa_url; + } - public void setOa_url(String oa_url) { - this.oa_url = oa_url; - } + public void setOa_url(String oa_url) { + this.oa_url = oa_url; + } - public String getOa_collectedfrom() { - return oa_collectedfrom; - } + public String getOa_collectedfrom() { + return oa_collectedfrom; + } - public void setOa_collectedfrom(String oa_collectedfrom) { - this.oa_collectedfrom = oa_collectedfrom; - } + public void setOa_collectedfrom(String oa_collectedfrom) { + this.oa_collectedfrom = oa_collectedfrom; + } + + @Override + public String toString() { + return "OrgSimRel{" + + "local_id='" + local_id + '\'' + + ", oa_original_id='" + oa_original_id + '\'' + + ", oa_name='" + oa_name + '\'' + + ", oa_acronym='" + oa_acronym + '\'' + + ", oa_country='" + oa_country + '\'' + + ", oa_url='" + oa_url + '\'' + + ", oa_collectedfrom='" + oa_collectedfrom + '\'' + + '}'; + } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/PidType.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/PidType.java index ab5c49868..c3241bac6 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/PidType.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/PidType.java @@ -1,25 +1,17 @@ + package eu.dnetlib.dhp.oa.dedup; public enum PidType { - //from the less to the more important - undefined, - original, - orcid, - ror, - grid, - pdb, - arXiv, - pmid, - doi; + // from the less to the more important + undefined, original, orcid, ror, grid, pdb, arXiv, pmid, doi; - public static PidType classidValueOf(String s){ - try { - return PidType.valueOf(s); - } - catch (Exception e) { - return PidType.undefined; - } - } + public static PidType classidValueOf(String s) { + try { + return PidType.valueOf(s); + } catch (Exception e) { + return PidType.undefined; + } + } -} \ No newline at end of file +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCollectSimRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCollectSimRels.java index b35f9c54a..f9e6448b0 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCollectSimRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCollectSimRels.java @@ -1,21 +1,5 @@ -package eu.dnetlib.dhp.oa.dedup; -import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.schema.oaf.DataInfo; -import eu.dnetlib.dhp.schema.oaf.Relation; -import eu.dnetlib.dhp.utils.ISLookupClientFactory; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import eu.dnetlib.pace.config.DedupConfig; -import org.apache.commons.io.IOUtils; -import org.apache.spark.SparkConf; -import org.apache.spark.api.java.JavaPairRDD; -import org.apache.spark.api.java.JavaRDD; -import org.apache.spark.sql.*; -import org.dom4j.DocumentException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import scala.Tuple2; +package eu.dnetlib.dhp.oa.dedup; import java.io.IOException; import java.util.ArrayList; @@ -24,153 +8,177 @@ import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.StreamSupport; +import org.apache.commons.io.IOUtils; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaPairRDD; +import org.apache.spark.api.java.JavaRDD; +import org.apache.spark.sql.*; +import org.dom4j.DocumentException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.oaf.DataInfo; +import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import eu.dnetlib.pace.config.DedupConfig; +import scala.Tuple2; + public class SparkCollectSimRels extends AbstractSparkAction { - private static final Logger log = LoggerFactory.getLogger(SparkCollectSimRels.class); + private static final Logger log = LoggerFactory.getLogger(SparkCollectSimRels.class); - Dataset simGroupsDS; - Dataset groupsDS; + Dataset simGroupsDS; + Dataset groupsDS; - public SparkCollectSimRels(ArgumentApplicationParser parser, SparkSession spark, Dataset simGroupsDS, Dataset groupsDS) { - super(parser, spark); - this.simGroupsDS = simGroupsDS; - this.groupsDS = groupsDS; - } + public SparkCollectSimRels(ArgumentApplicationParser parser, SparkSession spark, Dataset simGroupsDS, + Dataset groupsDS) { + super(parser, spark); + this.simGroupsDS = simGroupsDS; + this.groupsDS = groupsDS; + } - public static void main(String[] args) throws Exception { - ArgumentApplicationParser parser = new ArgumentApplicationParser( - IOUtils - .toString( - SparkBlockStats.class - .getResourceAsStream( - "/eu/dnetlib/dhp/oa/dedup/collectSimRels_parameters.json"))); - parser.parseArgument(args); + public static void main(String[] args) throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkBlockStats.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/collectSimRels_parameters.json"))); + parser.parseArgument(args); - SparkConf conf = new SparkConf(); + SparkConf conf = new SparkConf(); - final String dbUrl = parser.get("postgresUrl"); - final String dbUser = parser.get("postgresUser"); - final String dbPassword = parser.get("postgresPassword"); + final String dbUrl = parser.get("postgresUrl"); + final String dbUser = parser.get("postgresUser"); + final String dbPassword = parser.get("postgresPassword"); - SparkSession spark = getSparkSession(conf); + SparkSession spark = getSparkSession(conf); - DataFrameReader readOptions = spark.read() - .format("jdbc") - .option("url", dbUrl) - .option("user", dbUser) - .option("password", dbPassword); + DataFrameReader readOptions = spark + .read() + .format("jdbc") + .option("url", dbUrl) + .option("user", dbUser) + .option("password", dbPassword); - new SparkCollectSimRels( - parser, - spark, - readOptions.option("dbtable", "similarity_groups").load(), - readOptions.option("dbtable", "groups").load() - ).run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); - } + new SparkCollectSimRels( + parser, + spark, + readOptions.option("dbtable", "similarity_groups").load(), + readOptions.option("dbtable", "groups").load()) + .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); + } - @Override - void run(ISLookUpService isLookUpService) throws DocumentException, ISLookUpException, IOException { + @Override + void run(ISLookUpService isLookUpService) throws DocumentException, ISLookUpException, IOException { - // read oozie parameters - final String isLookUpUrl = parser.get("isLookUpUrl"); - final String actionSetId = parser.get("actionSetId"); - final String workingPath = parser.get("workingPath"); - final int numPartitions = Optional - .ofNullable(parser.get("numPartitions")) - .map(Integer::valueOf) - .orElse(NUM_PARTITIONS); - final String dbUrl = parser.get("postgresUrl"); - final String dbUser = parser.get("postgresUser"); + // read oozie parameters + final String isLookUpUrl = parser.get("isLookUpUrl"); + final String actionSetId = parser.get("actionSetId"); + final String workingPath = parser.get("workingPath"); + final int numPartitions = Optional + .ofNullable(parser.get("numPartitions")) + .map(Integer::valueOf) + .orElse(NUM_PARTITIONS); + final String dbUrl = parser.get("postgresUrl"); + final String dbUser = parser.get("postgresUser"); - log.info("numPartitions: '{}'", numPartitions); - log.info("isLookUpUrl: '{}'", isLookUpUrl); - log.info("actionSetId: '{}'", actionSetId); - log.info("workingPath: '{}'", workingPath); - log.info("postgresUser: {}", dbUser); - log.info("postgresUrl: {}", dbUrl); - log.info("postgresPassword: xxx"); + log.info("numPartitions: '{}'", numPartitions); + log.info("isLookUpUrl: '{}'", isLookUpUrl); + log.info("actionSetId: '{}'", actionSetId); + log.info("workingPath: '{}'", workingPath); + log.info("postgresUser: {}", dbUser); + log.info("postgresUrl: {}", dbUrl); + log.info("postgresPassword: xxx"); - JavaPairRDD> similarityGroup = - simGroupsDS - .toJavaRDD() - .mapToPair(r -> new Tuple2<>(r.getString(0), r.getString(1))) - .groupByKey() - .mapToPair(i -> new Tuple2<>(i._1(), StreamSupport.stream(i._2().spliterator(), false) - .collect(Collectors.toList()))); + JavaPairRDD> similarityGroup = simGroupsDS + .toJavaRDD() + .mapToPair(r -> new Tuple2<>(r.getString(0), r.getString(1))) + .groupByKey() + .mapToPair( + i -> new Tuple2<>(i._1(), StreamSupport + .stream(i._2().spliterator(), false) + .collect(Collectors.toList()))); - JavaPairRDD groupIds = - groupsDS - .toJavaRDD() - .mapToPair(r -> new Tuple2<>(r.getString(0), r.getString(1))); + JavaPairRDD groupIds = groupsDS + .toJavaRDD() + .mapToPair(r -> new Tuple2<>(r.getString(0), r.getString(1))); - JavaRDD, List>> groups = similarityGroup - .leftOuterJoin(groupIds) - .filter(g -> g._2()._2().isPresent()) - .map(g -> new Tuple2<>(new Tuple2<>(g._1(), g._2()._2().get()), g._2()._1())); + JavaRDD, List>> groups = similarityGroup + .leftOuterJoin(groupIds) + .filter(g -> g._2()._2().isPresent()) + .map(g -> new Tuple2<>(new Tuple2<>(g._1(), g._2()._2().get()), g._2()._1())); - JavaRDD relations = groups.flatMap(g -> { - String firstId = g._2().get(0); - List rels = new ArrayList<>(); + JavaRDD relations = groups.flatMap(g -> { + String firstId = g._2().get(0); + List rels = new ArrayList<>(); - for (String id : g._2()) { - if (!firstId.equals(id)) - rels.add(createSimRel(firstId, id, g._1()._2())); - } + for (String id : g._2()) { + if (!firstId.equals(id)) + rels.add(createSimRel(firstId, id, g._1()._2())); + } - return rels.iterator(); - }); + return rels.iterator(); + }); - Dataset resultRelations = spark.createDataset( - relations.filter(r -> r.getRelType().equals("resultResult")).rdd(), - Encoders.bean(Relation.class) - ).repartition(numPartitions); + Dataset resultRelations = spark + .createDataset( + relations.filter(r -> r.getRelType().equals("resultResult")).rdd(), + Encoders.bean(Relation.class)) + .repartition(numPartitions); - Dataset organizationRelations = spark.createDataset( - relations.filter(r -> r.getRelType().equals("organizationOrganization")).rdd(), - Encoders.bean(Relation.class) - ).repartition(numPartitions); + Dataset organizationRelations = spark + .createDataset( + relations.filter(r -> r.getRelType().equals("organizationOrganization")).rdd(), + Encoders.bean(Relation.class)) + .repartition(numPartitions); - for (DedupConfig dedupConf : getConfigurations(isLookUpService, actionSetId)) { - switch(dedupConf.getWf().getSubEntityValue()){ - case "organization": - savePostgresRelation(organizationRelations, workingPath, actionSetId, "organization"); - break; - default: - savePostgresRelation(resultRelations, workingPath, actionSetId, dedupConf.getWf().getSubEntityValue()); - break; - } - } + for (DedupConfig dedupConf : getConfigurations(isLookUpService, actionSetId)) { + switch (dedupConf.getWf().getSubEntityValue()) { + case "organization": + savePostgresRelation(organizationRelations, workingPath, actionSetId, "organization"); + break; + default: + savePostgresRelation( + resultRelations, workingPath, actionSetId, dedupConf.getWf().getSubEntityValue()); + break; + } + } - } + } - private Relation createSimRel(String source, String target, String entity) { - final Relation r = new Relation(); - r.setSubRelType("dedupSimilarity"); - r.setRelClass("isSimilarTo"); - r.setDataInfo(new DataInfo()); + private Relation createSimRel(String source, String target, String entity) { + final Relation r = new Relation(); + r.setSubRelType("dedupSimilarity"); + r.setRelClass("isSimilarTo"); + r.setDataInfo(new DataInfo()); - switch (entity) { - case "result": - r.setSource("50|" + source); - r.setTarget("50|" + target); - r.setRelType("resultResult"); - break; - case "organization": - r.setSource("20|" + source); - r.setTarget("20|" + target); - r.setRelType("organizationOrganization"); - break; - default: - throw new IllegalArgumentException("unmanaged entity type: " + entity); - } - return r; - } + switch (entity) { + case "result": + r.setSource("50|" + source); + r.setTarget("50|" + target); + r.setRelType("resultResult"); + break; + case "organization": + r.setSource("20|" + source); + r.setTarget("20|" + target); + r.setRelType("organizationOrganization"); + break; + default: + throw new IllegalArgumentException("unmanaged entity type: " + entity); + } + return r; + } - private void savePostgresRelation(Dataset newRelations, String workingPath, String actionSetId, String entityType) { - newRelations - .write() - .mode(SaveMode.Append) - .parquet(DedupUtility.createSimRelPath(workingPath, actionSetId, entityType)); - } + private void savePostgresRelation(Dataset newRelations, String workingPath, String actionSetId, + String entityType) { + newRelations + .write() + .mode(SaveMode.Append) + .parquet(DedupUtility.createSimRelPath(workingPath, actionSetId, entityType)); + } -} \ No newline at end of file +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateMergeRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateMergeRels.java index ce6226dde..1122b42eb 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateMergeRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateMergeRels.java @@ -104,13 +104,13 @@ public class SparkCreateMergeRels extends AbstractSparkAction { .map(s -> MapDocumentUtil.getJPathString(dedupConf.getWf().getIdPath(), s)) .mapToPair((PairFunction) s -> new Tuple2<>(hash(s), s)); - final RDD> edgeRdd = spark - .read() - .load(DedupUtility.createSimRelPath(workingPath, actionSetId, subEntity)) - .as(Encoders.bean(Relation.class)) - .javaRDD() - .map(it -> new Edge<>(hash(it.getSource()), hash(it.getTarget()), it.getRelClass())) - .rdd(); + final RDD> edgeRdd = spark + .read() + .load(DedupUtility.createSimRelPath(workingPath, actionSetId, subEntity)) + .as(Encoders.bean(Relation.class)) + .javaRDD() + .map(it -> new Edge<>(hash(it.getSource()), hash(it.getTarget()), it.getRelClass())) + .rdd(); final Dataset mergeRels = spark .createDataset( diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateSimRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateSimRels.java index babccefb4..d5033d425 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateSimRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateSimRels.java @@ -100,17 +100,17 @@ public class SparkCreateSimRels extends AbstractSparkAction { .repartition(numPartitions); // create relations by comparing only elements in the same group - spark.createDataset( - Deduper - .computeRelations(sc, blocks, dedupConf) - .map(t -> createSimRel(t._1(), t._2(), entity)) - .repartition(numPartitions) - .rdd(), - Encoders.bean(Relation.class) - ) - .write() - .mode(SaveMode.Append) - .parquet(outputPath); + spark + .createDataset( + Deduper + .computeRelations(sc, blocks, dedupConf) + .map(t -> createSimRel(t._1(), t._2(), entity)) + .repartition(numPartitions) + .rdd(), + Encoders.bean(Relation.class)) + .write() + .mode(SaveMode.Append) + .parquet(outputPath); } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java index dff1bbb0d..d6c548de3 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java @@ -1,13 +1,11 @@ + package eu.dnetlib.dhp.oa.dedup; -import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.schema.common.EntityType; -import eu.dnetlib.dhp.schema.common.ModelSupport; -import eu.dnetlib.dhp.schema.oaf.*; -import eu.dnetlib.dhp.utils.ISLookupClientFactory; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import eu.dnetlib.pace.config.DedupConfig; +import static jdk.nashorn.internal.objects.NativeDebug.map; + +import java.io.IOException; +import java.util.*; + import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; @@ -15,6 +13,7 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaRDD; +import org.apache.spark.api.java.function.FilterFunction; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.api.java.function.MapGroupsFunction; import org.apache.spark.sql.Dataset; @@ -24,145 +23,172 @@ import org.apache.spark.sql.SparkSession; import org.dom4j.DocumentException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import scala.Tuple2; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Properties; +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.common.EntityType; +import eu.dnetlib.dhp.schema.common.ModelSupport; +import eu.dnetlib.dhp.schema.oaf.*; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import eu.dnetlib.pace.config.DedupConfig; +import scala.Tuple2; public class SparkPrepareOrgRels extends AbstractSparkAction { - private static final Logger log = LoggerFactory.getLogger(SparkCreateDedupRecord.class); + private static final Logger log = LoggerFactory.getLogger(SparkCreateDedupRecord.class); - public static final String ROOT_TRUST = "0.8"; - public static final String PROVENANCE_ACTION_CLASS = "sysimport:dedup"; - public static final String PROVENANCE_ACTIONS = "dnet:provenanceActions"; + public SparkPrepareOrgRels(ArgumentApplicationParser parser, SparkSession spark) { + super(parser, spark); + } - public SparkPrepareOrgRels(ArgumentApplicationParser parser, SparkSession spark) { - super(parser, spark); - } + public static void main(String[] args) throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCreateSimRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json"))); + parser.parseArgument(args); - public static void main(String[] args) throws Exception { - ArgumentApplicationParser parser = new ArgumentApplicationParser( - IOUtils - .toString( - SparkCreateSimRels.class - .getResourceAsStream( - "/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json"))); - parser.parseArgument(args); + SparkConf conf = new SparkConf(); + conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer"); + conf.registerKryoClasses(ModelSupport.getOafModelClasses()); - SparkConf conf = new SparkConf(); - conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer"); - conf.registerKryoClasses(ModelSupport.getOafModelClasses()); + new SparkPrepareOrgRels(parser, getSparkSession(conf)) + .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); + } - new SparkCreateDedupRecord(parser, getSparkSession(conf)) - .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); - } + @Override + public void run(ISLookUpService isLookUpService) throws IOException { - @Override - public void run(ISLookUpService isLookUpService) throws IOException { + final String graphBasePath = parser.get("graphBasePath"); + final String isLookUpUrl = parser.get("isLookUpUrl"); + final String actionSetId = parser.get("actionSetId"); + final String workingPath = parser.get("workingPath"); + final int numConnections = Optional + .ofNullable(parser.get("numConnections")) + .map(Integer::valueOf) + .orElse(NUM_CONNECTIONS); - final String graphBasePath = parser.get("graphBasePath"); - final String isLookUpUrl = parser.get("isLookUpUrl"); - final String actionSetId = parser.get("actionSetId"); - final String workingPath = parser.get("workingPath"); - final String apiUrl = parser.get("apiUrl"); - final String dbUrl = parser.get("dbUrl"); - final String dbTable = parser.get("dbTable"); - final String dbUser = parser.get("dbUser"); - final String dbPwd = parser.get("dbPwd"); + final String apiUrl = Optional + .ofNullable(parser.get("apiUrl")) + .orElse(""); - log.info("graphBasePath: '{}'", graphBasePath); - log.info("isLookUpUrl: '{}'", isLookUpUrl); - log.info("actionSetId: '{}'", actionSetId); - log.info("workingPath: '{}'", workingPath); - log.info("apiUrl: '{}'", apiUrl); - log.info("dbUrl: '{}'", dbUrl); - log.info("dbUser: '{}'", dbUser); - log.info("table: '{}'", dbTable); - log.info("dbPwd: '{}'", "xxx"); + final String dbUrl = parser.get("dbUrl"); + final String dbTable = parser.get("dbTable"); + final String dbUser = parser.get("dbUser"); + final String dbPwd = parser.get("dbPwd"); - final String mergeRelPath = DedupUtility.createMergeRelPath(workingPath, actionSetId, "organization"); - final String entityPath = DedupUtility.createEntityPath(graphBasePath, "organization"); + log.info("graphBasePath: '{}'", graphBasePath); + log.info("isLookUpUrl: '{}'", isLookUpUrl); + log.info("actionSetId: '{}'", actionSetId); + log.info("workingPath: '{}'", workingPath); + log.info("numPartitions: '{}'", numConnections); + log.info("apiUrl: '{}'", apiUrl); + log.info("dbUrl: '{}'", dbUrl); + log.info("dbUser: '{}'", dbUser); + log.info("table: '{}'", dbTable); + log.info("dbPwd: '{}'", "xxx"); - Dataset relations = createRelations(spark, mergeRelPath, entityPath); + final String mergeRelPath = DedupUtility.createMergeRelPath(workingPath, actionSetId, "organization"); + final String entityPath = DedupUtility.createEntityPath(graphBasePath, "organization"); - final Properties connectionProperties = new Properties(); - connectionProperties.put("user", dbUser); - connectionProperties.put("password", dbPwd); + Dataset relations = createRelations(spark, mergeRelPath, entityPath); - relations.write().mode(SaveMode.Overwrite).jdbc(dbUrl, dbTable, connectionProperties); + final Properties connectionProperties = new Properties(); + connectionProperties.put("user", dbUser); + connectionProperties.put("password", dbPwd); - if (!apiUrl.isEmpty()) - updateSimRels(apiUrl); + relations + .repartition(numConnections) + .write() + .mode(SaveMode.Overwrite) + .jdbc(dbUrl, dbTable, connectionProperties); - } + if (!apiUrl.isEmpty()) + updateSimRels(apiUrl); - public static Dataset createRelations( - final SparkSession spark, - final String mergeRelsPath, - final String entitiesPath) { + } - // - Dataset> entities = spark - .read() - .textFile(entitiesPath) - .map( - (MapFunction>) it -> { - Organization entity = OBJECT_MAPPER.readValue(it, Organization.class); - return new Tuple2<>(entity.getId(), entity); - }, - Encoders.tuple(Encoders.STRING(), Encoders.kryo(Organization.class))); + public static Dataset createRelations( + final SparkSession spark, + final String mergeRelsPath, + final String entitiesPath) { - Dataset> relations = spark.createDataset( - spark - .read() - .load(mergeRelsPath) - .as(Encoders.bean(Relation.class)) - .where("relClass == 'merges'") - .toJavaRDD() - .mapToPair(r -> new Tuple2<>(r.getSource(), r.getTarget())) - .groupByKey() - .flatMap(g -> { - List> rels = new ArrayList<>(); - for (String id1 : g._2()) { - for (String id2 : g._2()) { - if (!id1.equals(id2)) - if (id1.contains("openorgs")) - rels.add(new Tuple2<>(id1, id2)); - } - } - return rels.iterator(); - }).rdd(), - Encoders.tuple(Encoders.STRING(), Encoders.STRING())); + // + Dataset> entities = spark + .read() + .textFile(entitiesPath) + .map( + (MapFunction>) it -> { + Organization entity = OBJECT_MAPPER.readValue(it, Organization.class); + return new Tuple2<>(entity.getId(), entity); + }, + Encoders.tuple(Encoders.STRING(), Encoders.kryo(Organization.class))); - return relations - .joinWith(entities, relations.col("_2").equalTo(entities.col("_1")), "inner") - .map( - (MapFunction, Tuple2>, OrgSimRel>)r -> - new OrgSimRel( - r._1()._2(), - r._2()._2().getOriginalId().get(0), - r._2()._2().getLegalname().getValue(), - r._2()._2().getLegalshortname().getValue(), - r._2()._2().getCountry().getClassid(), - r._2()._2().getWebsiteurl().getValue(), - r._2()._2().getCollectedfrom().get(0).getValue() - ), - Encoders.bean(OrgSimRel.class) - ); + Dataset> relations = spark + .createDataset( + spark + .read() + .load(mergeRelsPath) + .as(Encoders.bean(Relation.class)) + .where("relClass == 'merges'") + .toJavaRDD() + .mapToPair(r -> new Tuple2<>(r.getSource(), r.getTarget())) + .groupByKey() + .flatMap(g -> { + List> rels = new ArrayList<>(); + for (String id1 : g._2()) { + for (String id2 : g._2()) { + if (!id1.equals(id2)) + if (id1.contains("openorgs____") && !id2.contains("openorgsmesh")) + rels.add(new Tuple2<>(id1, id2)); + } + } + return rels.iterator(); + }) + .rdd(), + Encoders.tuple(Encoders.STRING(), Encoders.STRING())); - } + Dataset> relations2 = relations // + .joinWith(entities, relations.col("_2").equalTo(entities.col("_1")), "inner") + .map( + (MapFunction, Tuple2>, OrgSimRel>) r -> new OrgSimRel( + r._1()._1(), + r._2()._2().getOriginalId().get(0), + r._2()._2().getLegalname() != null ? r._2()._2().getLegalname().getValue() : "", + r._2()._2().getLegalshortname() != null ? r._2()._2().getLegalshortname().getValue() : "", + r._2()._2().getCountry() != null ? r._2()._2().getCountry().getClassid() : "", + r._2()._2().getWebsiteurl() != null ? r._2()._2().getWebsiteurl().getValue() : "", + r._2()._2().getCollectedfrom().get(0).getValue()), + Encoders.bean(OrgSimRel.class)) + .map( + (MapFunction>) o -> new Tuple2<>(o.getLocal_id(), o), + Encoders.tuple(Encoders.STRING(), Encoders.bean(OrgSimRel.class))); - private static String updateSimRels(final String apiUrl) throws IOException { - final HttpGet req = new HttpGet(apiUrl); - try (final CloseableHttpClient client = HttpClients.createDefault()) { - try (final CloseableHttpResponse response = client.execute(req)) { - return IOUtils.toString(response.getEntity().getContent()); - } - } - } + return relations2 + .joinWith(entities, relations2.col("_1").equalTo(entities.col("_1")), "inner") + .map( + (MapFunction, Tuple2>, OrgSimRel>) r -> { + OrgSimRel orgSimRel = r._1()._2(); + orgSimRel.setLocal_id(r._2()._2().getOriginalId().get(0)); + return orgSimRel; + }, + Encoders.bean(OrgSimRel.class)); + + } + + private static String updateSimRels(final String apiUrl) throws IOException { + + log.info("Updating simrels on the portal"); + + final HttpGet req = new HttpGet(apiUrl); + try (final CloseableHttpClient client = HttpClients.createDefault()) { + try (final CloseableHttpResponse response = client.execute(req)) { + return IOUtils.toString(response.getEntity().getContent()); + } + } + } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/workflow.xml index 82ecbc46b..ec9967d6a 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/workflow.xml @@ -1,4 +1,4 @@ - + graphBasePath @@ -24,10 +24,6 @@ cutConnectedComponent max number of elements in a connected component - - apiUrl - the url for the APIs of the openorgs service - dbUrl the url of the database @@ -109,6 +105,16 @@ + + + + + + + -pb + ${graphBasePath}/relation + ${workingPath}/${actionSetId}/organization_simrel + @@ -136,16 +142,6 @@ --workingPath${workingPath} --numPartitions8000 - - - - - - - -pb - ${graphBasePath}/relation - ${workingPath}/organization_simrel - @@ -203,6 +199,7 @@ --dbTable${dbTable} --dbUser${dbUser} --dbPwd${dbPwd} + --numConnections20 diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json index bcca48a15..b70d1af28 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json @@ -23,11 +23,17 @@ "paramDescription": "the id of the actionset (orchestrator)", "paramRequired": true }, + { + "paramName": "nc", + "paramLongName": "numConnections", + "paramDescription": "number of connections to the postgres db (for the write operation)", + "paramRequired": false + }, { "paramName": "au", "paramLongName": "apiUrl", "paramDescription": "the url for the APIs of the openorgs service", - "paramRequired": true + "paramRequired": false }, { "paramName": "du", diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java index 431751584..30b213ff2 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java @@ -138,10 +138,10 @@ public class EntityMergerTest implements Serializable { public void publicationMergerTest3() throws InstantiationException, IllegalAccessException { Publication pub_merged = DedupRecordFactory - .entityMerger(dedupId, publications3.iterator(), 0, dataInfo, Publication.class); + .entityMerger(dedupId, publications3.iterator(), 0, dataInfo, Publication.class); // verify id - assertEquals( "50|dedup_doi___::0ca46ff10b2b4c756191719d85302b14", pub_merged.getId()); + assertEquals("50|dedup_doi___::0ca46ff10b2b4c756191719d85302b14", pub_merged.getId()); } @@ -149,7 +149,7 @@ public class EntityMergerTest implements Serializable { public void publicationMergerTest4() throws InstantiationException, IllegalStateException, IllegalAccessException { Publication pub_merged = DedupRecordFactory - .entityMerger(dedupId, publications4.iterator(), 0, dataInfo, Publication.class); + .entityMerger(dedupId, publications4.iterator(), 0, dataInfo, Publication.class); // verify id assertEquals("50|dedup_wf_001::2d2bbbbcfb285e3fb3590237b79e2fa8", pub_merged.getId()); @@ -160,7 +160,7 @@ public class EntityMergerTest implements Serializable { public void publicationMergerTest5() throws InstantiationException, IllegalStateException, IllegalAccessException { Publication pub_merged = DedupRecordFactory - .entityMerger(dedupId, publications5.iterator(), 0, dataInfo, Publication.class); + .entityMerger(dedupId, publications5.iterator(), 0, dataInfo, Publication.class); // verify id assertEquals("50|dedup_wf_001::584b89679c3ccd1015b647ec63cc2699", pub_merged.getId()); diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java index 59c850591..d9b5e30eb 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java @@ -1,11 +1,18 @@ + package eu.dnetlib.dhp.oa.dedup; -import com.fasterxml.jackson.databind.ObjectMapper; -import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.schema.oaf.Relation; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import eu.dnetlib.pace.util.MapDocumentUtil; +import static java.nio.file.Files.createTempDirectory; + +import static org.apache.spark.sql.functions.count; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.lenient; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.net.URISyntaxException; +import java.nio.file.Paths; + import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; @@ -21,19 +28,16 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import eu.dnetlib.pace.util.MapDocumentUtil; import scala.Tuple2; -import java.io.File; -import java.io.IOException; -import java.io.Serializable; -import java.net.URISyntaxException; -import java.nio.file.Paths; - -import static java.nio.file.Files.createTempDirectory; -import static org.apache.spark.sql.functions.count; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.lenient; - @ExtendWith(MockitoExtension.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class SparkDedupTest implements Serializable { @@ -48,7 +52,7 @@ public class SparkDedupTest implements Serializable { private static String testOutputBasePath; private static String testDedupGraphBasePath; private static final String testActionSetId = "test-orchestrator"; - private static String testDedupAssertionsBasePath; + private static String testDedupAssertionsBasePath; @BeforeAll public static void cleanUp() throws IOException, URISyntaxException { @@ -64,9 +68,9 @@ public class SparkDedupTest implements Serializable { .toAbsolutePath() .toString(); testDedupAssertionsBasePath = Paths - .get(SparkDedupTest.class.getResource("/eu/dnetlib/dhp/dedup/assertions").toURI()) - .toFile() - .getAbsolutePath(); + .get(SparkDedupTest.class.getResource("/eu/dnetlib/dhp/dedup/assertions").toURI()) + .toFile() + .getAbsolutePath(); FileUtils.deleteDirectory(new File(testOutputBasePath)); FileUtils.deleteDirectory(new File(testDedupGraphBasePath)); @@ -82,7 +86,7 @@ public class SparkDedupTest implements Serializable { jsc = JavaSparkContext.fromSparkContext(spark.sparkContext()); - } + } @BeforeEach public void setUp() throws IOException, ISLookUpException { @@ -165,98 +169,98 @@ public class SparkDedupTest implements Serializable { new SparkCreateSimRels(parser, spark).run(isLookUpService); - long orgs_simrel = spark - .read() - .load(testOutputBasePath + "/" + testActionSetId + "/organization_simrel") - .count(); + long orgs_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/organization_simrel") + .count(); - long pubs_simrel = spark - .read() - .load(testOutputBasePath + "/" + testActionSetId + "/publication_simrel") - .count(); + long pubs_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/publication_simrel") + .count(); - long sw_simrel = spark - .read() - .load(testOutputBasePath + "/" + testActionSetId + "/software_simrel") - .count(); + long sw_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/software_simrel") + .count(); - long ds_simrel = spark - .read() - .load(testOutputBasePath + "/" + testActionSetId + "/dataset_simrel") - .count(); + long ds_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/dataset_simrel") + .count(); - long orp_simrel = spark - .read() - .load(testOutputBasePath + "/" + testActionSetId + "/otherresearchproduct_simrel") - .count(); + long orp_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/otherresearchproduct_simrel") + .count(); - assertEquals(3432, orgs_simrel); - assertEquals(7152, pubs_simrel); + assertEquals(3082, orgs_simrel); + assertEquals(7036, pubs_simrel); assertEquals(344, sw_simrel); - assertEquals(458, ds_simrel); + assertEquals(442, ds_simrel); assertEquals(6750, orp_simrel); } - @Test - @Order(2) - public void collectSimRelsTest() throws Exception { - ArgumentApplicationParser parser = new ArgumentApplicationParser( - IOUtils - .toString( - SparkCreateSimRels.class - .getResourceAsStream( - "/eu/dnetlib/dhp/oa/dedup/collectSimRels_parameters.json"))); - parser - .parseArgument( - new String[] { - "-asi", testActionSetId, - "-la", "lookupurl", - "-w", testOutputBasePath, - "-np", "50", - "-purl", "jdbc:postgresql://localhost:5432/dnet_dedup", - "-pusr", "postgres_url", - "-ppwd", "" - }); + @Test + @Order(2) + public void collectSimRelsTest() throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCollectSimRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/collectSimRels_parameters.json"))); + parser + .parseArgument( + new String[] { + "-asi", testActionSetId, + "-la", "lookupurl", + "-w", testOutputBasePath, + "-np", "50", + "-purl", "jdbc:postgresql://localhost:5432/dnet_dedup", + "-pusr", "postgres_user", + "-ppwd", "" + }); - new SparkCollectSimRels( - parser, - spark, - spark.read().load(testDedupAssertionsBasePath + "/similarity_groups"), - spark.read().load(testDedupAssertionsBasePath + "/groups") - ).run(null); + new SparkCollectSimRels( + parser, + spark, + spark.read().load(testDedupAssertionsBasePath + "/similarity_groups"), + spark.read().load(testDedupAssertionsBasePath + "/groups")) + .run(isLookUpService); - long orgs_simrel = spark - .read() - .load(testOutputBasePath + "/" + testActionSetId + "/organization_simrel") - .count(); + long orgs_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/organization_simrel") + .count(); - long pubs_simrel = spark - .read() - .load(testOutputBasePath + "/" + testActionSetId + "/publication_simrel") - .count(); + long pubs_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/publication_simrel") + .count(); - long sw_simrel = spark - .read() - .load(testOutputBasePath + "/" + testActionSetId + "/software_simrel") - .count(); + long sw_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/software_simrel") + .count(); - long ds_simrel = spark - .read() - .load(testOutputBasePath + "/" + testActionSetId + "/dataset_simrel") - .count(); + long ds_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/dataset_simrel") + .count(); - long orp_simrel = spark - .read() - .load(testOutputBasePath + "/" + testActionSetId + "/otherresearchproduct_simrel") - .count(); + long orp_simrel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/otherresearchproduct_simrel") + .count(); - assertEquals(4022, orgs_simrel); - assertEquals(10575, pubs_simrel); - assertEquals(3767, sw_simrel); - assertEquals(3881, ds_simrel); - assertEquals(10173, orp_simrel); + assertEquals(3672, orgs_simrel); + assertEquals(10459, pubs_simrel); + assertEquals(3767, sw_simrel); + assertEquals(3865, ds_simrel); + assertEquals(10173, orp_simrel); - } + } @Test @Order(3) @@ -402,8 +406,8 @@ public class SparkDedupTest implements Serializable { .load(testOutputBasePath + "/" + testActionSetId + "/otherresearchproduct_mergerel") .count(); - assertEquals(1276, orgs_mergerel); - assertEquals(1442, pubs_mergerel); + assertEquals(1272, orgs_mergerel); + assertEquals(1438, pubs_mergerel); assertEquals(288, sw_mergerel); assertEquals(472, ds_mergerel); assertEquals(718, orp_mergerel); @@ -449,10 +453,10 @@ public class SparkDedupTest implements Serializable { testOutputBasePath + "/" + testActionSetId + "/otherresearchproduct_deduprecord") .count(); - assertEquals(82, orgs_deduprecord); - assertEquals(66, pubs_deduprecord); + assertEquals(84, orgs_deduprecord); + assertEquals(65, pubs_deduprecord); assertEquals(51, sw_deduprecord); - assertEquals(96, ds_deduprecord); + assertEquals(97, ds_deduprecord); assertEquals(89, orp_deduprecord); } @@ -532,12 +536,12 @@ public class SparkDedupTest implements Serializable { .distinct() .count(); - assertEquals(897, publications); - assertEquals(835, organizations); + assertEquals(896, publications); + assertEquals(837, organizations); assertEquals(100, projects); assertEquals(100, datasource); assertEquals(200, softwares); - assertEquals(388, dataset); + assertEquals(389, dataset); assertEquals(517, otherresearchproduct); long deletedOrgs = jsc @@ -592,7 +596,7 @@ public class SparkDedupTest implements Serializable { long relations = jsc.textFile(testDedupGraphBasePath + "/relation").count(); - assertEquals(4866, relations); + assertEquals(4858, relations); // check deletedbyinference final Dataset mergeRels = spark @@ -641,11 +645,11 @@ public class SparkDedupTest implements Serializable { assertEquals(expected_unique, rel.distinct().count()); } -// @AfterAll -// public static void finalCleanUp() throws IOException { -// FileUtils.deleteDirectory(new File(testOutputBasePath)); -// FileUtils.deleteDirectory(new File(testDedupGraphBasePath)); -// } + @AfterAll + public static void finalCleanUp() throws IOException { + FileUtils.deleteDirectory(new File(testOutputBasePath)); + FileUtils.deleteDirectory(new File(testDedupGraphBasePath)); + } public boolean isDeletedByInference(String s) { return s.contains("\"deletedbyinference\":true"); diff --git a/pom.xml b/pom.xml index cec3dd75a..f7300260c 100644 --- a/pom.xml +++ b/pom.xml @@ -315,7 +315,7 @@ eu.dnetlib dnet-pace-core - 4.0.4 + 4.0.5 eu.dnetlib From a2ac7e52fb33057f36c81febd1bb226169d0b32a Mon Sep 17 00:00:00 2001 From: miconis Date: Tue, 6 Oct 2020 13:58:09 +0200 Subject: [PATCH 006/445] implementation of the workflow for new organizations in openorgs --- .../dhp/oa/dedup/SparkPrepareNewOrgs.java | 145 ++++++++++++ .../dhp/oa/dedup/SparkPrepareOrgRels.java | 17 +- .../dhp/oa/dedup/SparkPropagateRelation.java | 3 +- .../neworgs/oozie_app/config-default.xml | 18 ++ .../oa/dedup/neworgs/oozie_app/workflow.xml | 208 ++++++++++++++++++ .../oa/dedup/prepareNewOrgs_parameters.json | 56 +++++ .../dnetlib/dhp/oa/dedup/SparkDedupTest.java | 10 +- 7 files changed, 439 insertions(+), 18 deletions(-) create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/neworgs/oozie_app/config-default.xml create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/neworgs/oozie_app/workflow.xml create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareNewOrgs_parameters.json diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java new file mode 100644 index 000000000..beb4dbad7 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java @@ -0,0 +1,145 @@ + +package eu.dnetlib.dhp.oa.dedup; + +import java.io.IOException; +import java.util.Optional; +import java.util.Properties; + +import org.apache.commons.io.IOUtils; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.function.FilterFunction; +import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.SaveMode; +import org.apache.spark.sql.SparkSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.common.ModelSupport; +import eu.dnetlib.dhp.schema.oaf.Organization; +import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import scala.Tuple2; + +public class SparkPrepareNewOrgs extends AbstractSparkAction { + + private static final Logger log = LoggerFactory.getLogger(SparkCreateDedupRecord.class); + + public SparkPrepareNewOrgs(ArgumentApplicationParser parser, SparkSession spark) { + super(parser, spark); + } + + public static void main(String[] args) throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkPrepareNewOrgs.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/prepareNewOrgs_parameters.json"))); + parser.parseArgument(args); + + SparkConf conf = new SparkConf(); + conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer"); + conf.registerKryoClasses(ModelSupport.getOafModelClasses()); + + new SparkPrepareNewOrgs(parser, getSparkSession(conf)) + .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); + } + + @Override + public void run(ISLookUpService isLookUpService) throws IOException { + + final String graphBasePath = parser.get("graphBasePath"); + final String isLookUpUrl = parser.get("isLookUpUrl"); + final String actionSetId = parser.get("actionSetId"); + final String workingPath = parser.get("workingPath"); + final int numConnections = Optional + .ofNullable(parser.get("numConnections")) + .map(Integer::valueOf) + .orElse(NUM_CONNECTIONS); + + final String dbUrl = parser.get("dbUrl"); + final String dbTable = parser.get("dbTable"); + final String dbUser = parser.get("dbUser"); + final String dbPwd = parser.get("dbPwd"); + + log.info("graphBasePath: '{}'", graphBasePath); + log.info("isLookUpUrl: '{}'", isLookUpUrl); + log.info("actionSetId: '{}'", actionSetId); + log.info("workingPath: '{}'", workingPath); + log.info("numPartitions: '{}'", numConnections); + log.info("dbUrl: '{}'", dbUrl); + log.info("dbUser: '{}'", dbUser); + log.info("table: '{}'", dbTable); + log.info("dbPwd: '{}'", "xxx"); + + final String mergeRelPath = DedupUtility.createMergeRelPath(workingPath, actionSetId, "organization"); + final String entityPath = DedupUtility.createEntityPath(graphBasePath, "organization"); + + Dataset newOrgs = createNewOrgs(spark, mergeRelPath, entityPath); + + final Properties connectionProperties = new Properties(); + connectionProperties.put("user", dbUser); + connectionProperties.put("password", dbPwd); + + newOrgs + .repartition(numConnections) + .write() + .mode(SaveMode.Overwrite) + .jdbc(dbUrl, dbTable, connectionProperties); + + } + + public static Dataset createNewOrgs( + final SparkSession spark, + final String mergeRelsPath, + final String entitiesPath) { + + // + Dataset> entities = spark + .read() + .textFile(entitiesPath) + .map( + (MapFunction>) it -> { + Organization entity = OBJECT_MAPPER.readValue(it, Organization.class); + return new Tuple2<>(entity.getId(), entity); + }, + Encoders.tuple(Encoders.STRING(), Encoders.kryo(Organization.class))); + + Dataset> mergerels = spark + .createDataset( + spark + .read() + .load(mergeRelsPath) + .as(Encoders.bean(Relation.class)) + .where("relClass == 'isMergedIn'") + .toJavaRDD() + .mapToPair(r -> new Tuple2<>(r.getSource(), r.getTarget())) + .rdd(), + Encoders.tuple(Encoders.STRING(), Encoders.STRING())); + + return entities + .joinWith(mergerels, entities.col("_1").equalTo(mergerels.col("_1")), "left") + .filter((FilterFunction, Tuple2>>) t -> t._2() == null) + .filter( + (FilterFunction, Tuple2>>) t -> !t + ._1() + ._1() + .contains("openorgs")) + .map( + (MapFunction, Tuple2>, OrgSimRel>) r -> new OrgSimRel( + "", + r._1()._2().getOriginalId().get(0), + r._1()._2().getLegalname() != null ? r._1()._2().getLegalname().getValue() : "", + r._1()._2().getLegalshortname() != null ? r._1()._2().getLegalshortname().getValue() : "", + r._1()._2().getCountry() != null ? r._1()._2().getCountry().getClassid() : "", + r._1()._2().getWebsiteurl() != null ? r._1()._2().getWebsiteurl().getValue() : "", + r._1()._2().getCollectedfrom().get(0).getValue()), + Encoders.bean(OrgSimRel.class)); + + } + +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java index d6c548de3..5e0081d72 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java @@ -1,10 +1,11 @@ package eu.dnetlib.dhp.oa.dedup; -import static jdk.nashorn.internal.objects.NativeDebug.map; - import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Properties; import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.CloseableHttpResponse; @@ -12,26 +13,20 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.spark.SparkConf; -import org.apache.spark.api.java.JavaRDD; -import org.apache.spark.api.java.function.FilterFunction; import org.apache.spark.api.java.function.MapFunction; -import org.apache.spark.api.java.function.MapGroupsFunction; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoders; import org.apache.spark.sql.SaveMode; import org.apache.spark.sql.SparkSession; -import org.dom4j.DocumentException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.schema.common.EntityType; import eu.dnetlib.dhp.schema.common.ModelSupport; -import eu.dnetlib.dhp.schema.oaf.*; +import eu.dnetlib.dhp.schema.oaf.Organization; +import eu.dnetlib.dhp.schema.oaf.Relation; import eu.dnetlib.dhp.utils.ISLookupClientFactory; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import eu.dnetlib.pace.config.DedupConfig; import scala.Tuple2; public class SparkPrepareOrgRels extends AbstractSparkAction { diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPropagateRelation.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPropagateRelation.java index ae5bf9252..699039c99 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPropagateRelation.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPropagateRelation.java @@ -28,8 +28,7 @@ public class SparkPropagateRelation extends AbstractSparkAction { SOURCE, TARGET } - public SparkPropagateRelation(ArgumentApplicationParser parser, SparkSession spark) - throws Exception { + public SparkPropagateRelation(ArgumentApplicationParser parser, SparkSession spark) throws Exception { super(parser, spark); } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/neworgs/oozie_app/config-default.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/neworgs/oozie_app/config-default.xml new file mode 100644 index 000000000..2e0ed9aee --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/neworgs/oozie_app/config-default.xml @@ -0,0 +1,18 @@ + + + jobTracker + yarnRM + + + nameNode + hdfs://nameservice1 + + + oozie.use.system.libpath + true + + + oozie.action.sharelib.for.spark + spark2 + + \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/neworgs/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/neworgs/oozie_app/workflow.xml new file mode 100644 index 000000000..9bfdaaebd --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/neworgs/oozie_app/workflow.xml @@ -0,0 +1,208 @@ + + + + graphBasePath + the raw graph base path + + + isLookUpUrl + the address of the lookUp service + + + actionSetId + id of the actionSet + + + workingPath + path for the working directory + + + dedupGraphPath + path for the output graph + + + cutConnectedComponent + max number of elements in a connected component + + + dbUrl + the url of the database + + + dbUser + the user of the database + + + dbTable + the name of the table in the database + + + dbPwd + the passowrd of the user of the database + + + sparkDriverMemory + memory for driver process + + + sparkExecutorMemory + memory for individual executor + + + sparkExecutorCores + number of cores used by single executor + + + oozieActionShareLibForSpark2 + oozie action sharelib for spark 2.* + + + spark2ExtraListeners + com.cloudera.spark.lineage.NavigatorAppListener + spark 2.* extra listeners classname + + + spark2SqlQueryExecutionListeners + com.cloudera.spark.lineage.NavigatorQueryListener + spark 2.* sql query execution listeners classname + + + spark2YarnHistoryServerAddress + spark 2.* yarn history server address + + + spark2EventLogDir + spark 2.* event log dir location + + + + + ${jobTracker} + ${nameNode} + + + mapreduce.job.queuename + ${queueName} + + + oozie.launcher.mapred.job.queue.name + ${oozieLauncherQueueName} + + + oozie.action.sharelib.for.spark + ${oozieActionShareLibForSpark2} + + + + + + + + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] + + + + + + + + + + + + + -pb + ${graphBasePath}/relation + ${workingPath}/${actionSetId}/organization_simrel + + + + + + + + yarn + cluster + Create Similarity Relations + eu.dnetlib.dhp.oa.dedup.SparkCreateSimRels + dhp-dedup-openaire-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=3840 + + --graphBasePath${graphBasePath} + --isLookUpUrl${isLookUpUrl} + --actionSetId${actionSetId} + --workingPath${workingPath} + --numPartitions8000 + + + + + + + + yarn + cluster + Create Merge Relations + eu.dnetlib.dhp.oa.dedup.SparkCreateMergeRels + dhp-dedup-openaire-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=3840 + + --graphBasePath${graphBasePath} + --workingPath${workingPath} + --isLookUpUrl${isLookUpUrl} + --actionSetId${actionSetId} + --cutConnectedComponent${cutConnectedComponent} + + + + + + + + yarn + cluster + Prepare New Organizations + eu.dnetlib.dhp.oa.dedup.SparkPrepareNewOrgs + dhp-dedup-openaire-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=3840 + + --graphBasePath${graphBasePath} + --workingPath${workingPath} + --isLookUpUrl${isLookUpUrl} + --actionSetId${actionSetId} + --dbUrl${dbUrl} + --dbTable${dbTable} + --dbUser${dbUser} + --dbPwd${dbPwd} + --numConnections20 + + + + + + + \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareNewOrgs_parameters.json b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareNewOrgs_parameters.json new file mode 100644 index 000000000..2119cbc3a --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareNewOrgs_parameters.json @@ -0,0 +1,56 @@ +[ + { + "paramName": "i", + "paramLongName": "graphBasePath", + "paramDescription": "the base path of raw graph", + "paramRequired": true + }, + { + "paramName": "w", + "paramLongName": "workingPath", + "paramDescription": "the working directory path", + "paramRequired": true + }, + { + "paramName": "la", + "paramLongName": "isLookUpUrl", + "paramDescription": "the url of the lookup service", + "paramRequired": true + }, + { + "paramName": "asi", + "paramLongName": "actionSetId", + "paramDescription": "the id of the actionset (orchestrator)", + "paramRequired": true + }, + { + "paramName": "nc", + "paramLongName": "numConnections", + "paramDescription": "number of connections to the postgres db (for the write operation)", + "paramRequired": false + }, + { + "paramName": "du", + "paramLongName": "dbUrl", + "paramDescription": "the url of the database", + "paramRequired": true + }, + { + "paramName": "dusr", + "paramLongName": "dbUser", + "paramDescription": "the user of the database", + "paramRequired": true + }, + { + "paramName": "t", + "paramLongName": "dbTable", + "paramDescription": "the name of the table in the database", + "paramRequired": true + }, + { + "paramName": "dpwd", + "paramLongName": "dbPwd", + "paramDescription": "the password for the user of the database", + "paramRequired": true + } +] \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java index d9b5e30eb..6516eda52 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java @@ -223,11 +223,11 @@ public class SparkDedupTest implements Serializable { }); new SparkCollectSimRels( - parser, - spark, - spark.read().load(testDedupAssertionsBasePath + "/similarity_groups"), - spark.read().load(testDedupAssertionsBasePath + "/groups")) - .run(isLookUpService); + parser, + spark, + spark.read().load(testDedupAssertionsBasePath + "/similarity_groups"), + spark.read().load(testDedupAssertionsBasePath + "/groups")) + .run(isLookUpService); long orgs_simrel = spark .read() From 6ce340bd3d8ba3552a1640201d7003afd66c8141 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 6 Oct 2020 15:44:53 +0200 Subject: [PATCH 007/445] WIP stable ids: IdentifierFactory --- dhp-common/pom.xml | 6 ++ .../schema/oaf/utils/IdentifierFactory.java | 90 +++++++++++++++++++ .../dhp/schema/oaf/utils/PidComparator.java | 84 +++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java create mode 100644 dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidComparator.java diff --git a/dhp-common/pom.xml b/dhp-common/pom.xml index 1dc3208b5..6e7ee527b 100644 --- a/dhp-common/pom.xml +++ b/dhp-common/pom.xml @@ -29,6 +29,12 @@ spark-sql_2.11 + + eu.dnetlib.dhp + dhp-schemas + ${project.version} + + commons-cli commons-cli diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java new file mode 100644 index 000000000..02a946154 --- /dev/null +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -0,0 +1,90 @@ + +package eu.dnetlib.dhp.schema.oaf.utils; + +import eu.dnetlib.dhp.schema.oaf.OafEntity; +import eu.dnetlib.dhp.schema.oaf.StructuredProperty; +import eu.dnetlib.dhp.utils.DHPUtils; +import org.apache.commons.lang.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * Factory class for OpenAIRE identifiers in the Graph + */ +public class IdentifierFactory implements Serializable { + + private static final Logger log = LoggerFactory.getLogger(IdentifierFactory.class); + + public static final String ID_SEPARATOR = "::"; + public static final String ID_PREFIX_SEPARATOR = "|"; + public final static String ID_REGEX = "^[0-9][0-9]\\"+ID_PREFIX_SEPARATOR+".{12}"+ID_SEPARATOR+"[a-zA-Z0-9]{32}$"; + public static final int ID_PREFIX_LEN = 12; + + public static Set acceptedPidTypes = new HashSet<>(); + + static { + acceptedPidTypes.add("doi"); + acceptedPidTypes.add("doi"); + acceptedPidTypes.add("doi"); + acceptedPidTypes.add("doi"); + acceptedPidTypes.add("doi"); + acceptedPidTypes.add("doi"); + + } + + public static String createIdentifier(T entity) { + + if (Objects.isNull(entity.getPid()) || entity.getPid().isEmpty()) { + return entity.getId(); + } + + return entity + .getPid() + .stream() + .filter(s -> Objects.nonNull(s.getQualifier())) + .filter(s -> acceptedPidTypes.contains(s.getQualifier().getClassid())) + .max(new PidComparator(entity)) + .map(s -> idFromPid(entity, s)) + .map(IdentifierFactory::verifyIdSyntax) + .orElseGet(entity::getId); + } + + protected static String verifyIdSyntax(String s) { + if(StringUtils.isBlank(s) || !s.matches(ID_REGEX)) { + throw new RuntimeException(String.format("malformed id: '%s'", s)); + } else { + return s; + } + } + + private static String idFromPid(T entity, StructuredProperty s) { + return new StringBuilder() + .append(StringUtils.substringBefore(entity.getId(), ID_PREFIX_SEPARATOR)) + .append(ID_PREFIX_SEPARATOR) + .append(createPrefix(s.getQualifier().getClassid())) + .append(ID_SEPARATOR) + .append(DHPUtils.md5(normalizePidValue(s.getValue()))) + .toString(); + } + + private static String normalizePidValue(String value) { + //TODO more aggressive cleaning? keep only alphanum and punctation? + return value.toLowerCase().replaceAll(" ", ""); + } + + // create the prefix (length = 12) + private static String createPrefix(String pidType) { + StringBuilder prefix = new StringBuilder(StringUtils.left(pidType, ID_PREFIX_LEN)); + while (prefix.length() < ID_PREFIX_LEN) { + prefix.append("_"); + } + return prefix.substring(0, ID_PREFIX_LEN); + } + +} diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidComparator.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidComparator.java new file mode 100644 index 000000000..97bdd9c77 --- /dev/null +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidComparator.java @@ -0,0 +1,84 @@ + +package eu.dnetlib.dhp.schema.oaf.utils; + +import java.util.Comparator; + +import eu.dnetlib.dhp.schema.common.ModelSupport; +import eu.dnetlib.dhp.schema.oaf.OafEntity; +import eu.dnetlib.dhp.schema.oaf.Organization; +import eu.dnetlib.dhp.schema.oaf.Result; +import eu.dnetlib.dhp.schema.oaf.StructuredProperty; + +public class PidComparator implements Comparator { + + private T entity; + + public PidComparator(T entity) { + this.entity = entity; + } + + @Override + public int compare(StructuredProperty left, StructuredProperty right) { + + if (left == null && right == null) + return 0; + if (left == null) + return 1; + if (right == null) + return -1; + + String lClass = left.getQualifier().getClassid(); + String rClass = right.getQualifier().getClassid(); + + if (lClass.equals(rClass)) + return 0; + + if (ModelSupport.isSubClass(entity, Result.class)) { + return compareResultPids(lClass, rClass); + } + if (ModelSupport.isSubClass(entity, Organization.class)) { + return compareOrganizationtPids(lClass, rClass); + } + + // Else (but unlikely), lexicographical ordering will do. + return lClass.compareTo(rClass); + } + + private int compareResultPids(String lClass, String rClass) { + if (lClass.equals("doi")) + return -1; + if (rClass.equals("doi")) + return 1; + + if (lClass.equals("pmid")) + return -1; + if (rClass.equals("pmid")) + return 1; + + if (lClass.equals("pmc")) + return -1; + if (rClass.equals("pmc")) + return 1; + + return 0; + } + + private int compareOrganizationtPids(String lClass, String rClass) { + if (lClass.equals("GRID")) + return -1; + if (rClass.equals("GRID")) + return 1; + + if (lClass.equals("mag_id")) + return -1; + if (rClass.equals("mag_id")) + return 1; + + if (lClass.equals("urn")) + return -1; + if (rClass.equals("urn")) + return 1; + + return 0; + } +} From 70933554874d5a69f4652ef621453e5930259028 Mon Sep 17 00:00:00 2001 From: miconis Date: Tue, 6 Oct 2020 16:21:34 +0200 Subject: [PATCH 008/445] bug fix and minor changes --- .../dhp/oa/dedup/DedupRecordFactory.java | 6 +-- .../eu/dnetlib/dhp/oa/dedup/DedupUtility.java | 11 ------ .../eu/dnetlib/dhp/oa/dedup/IdGenerator.java | 7 ++-- .../eu/dnetlib/dhp/oa/dedup/Identifier.java | 37 ++++++++++--------- .../java/eu/dnetlib/dhp/oa/dedup/PidType.java | 2 +- .../oa/dedup/graph/ConnectedComponent.java | 3 +- 6 files changed, 27 insertions(+), 39 deletions(-) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java index 50dda887b..fd37b75ee 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java @@ -1,11 +1,8 @@ package eu.dnetlib.dhp.oa.dedup; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.*; -import org.apache.commons.lang.StringUtils; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.api.java.function.MapGroupsFunction; import org.apache.spark.sql.Dataset; @@ -18,7 +15,6 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; -import eu.dnetlib.dhp.schema.common.EntityType; import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.schema.oaf.*; import scala.Tuple2; @@ -90,7 +86,7 @@ public class DedupRecordFactory { T duplicate = t._2(); // prepare the list of pids to use for the id generation - bestPids.addAll(IdGenerator.bestPidtoIdentifier(duplicate)); + bestPids.addAll(IdGenerator.bestPidToIdentifier(duplicate)); entity.mergeFrom(duplicate); if (ModelSupport.isSubClass(duplicate, Result.class)) { diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupUtility.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupUtility.java index 01065510a..a44d51af3 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupUtility.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupUtility.java @@ -70,17 +70,6 @@ public class DedupUtility { return Sets.newHashSet(BlacklistAwareClusteringCombiner.filterAndCombine(doc, conf)); } - public static String md5(final String s) { - try { - final MessageDigest md = MessageDigest.getInstance("MD5"); - md.update(s.getBytes(StandardCharsets.UTF_8)); - return new String(Hex.encodeHex(md.digest())); - } catch (final Exception e) { - System.err.println("Error creating id"); - return null; - } - } - public static String createDedupRecordPath( final String basePath, final String actionSetId, final String entityType) { return String.format("%s/%s/%s_deduprecord", basePath, actionSetId, entityType); diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java index 2916e063d..b2b81f4cb 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java @@ -6,6 +6,7 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; +import eu.dnetlib.dhp.utils.DHPUtils; import org.apache.commons.lang.NullArgumentException; import org.apache.commons.lang.StringUtils; @@ -34,17 +35,17 @@ public class IdGenerator implements Serializable { if (bp.get().isUseOriginal() || bp.get().getPid().getValue() == null) { return bp.get().getOriginalID().split("\\|")[0] + "|dedup_wf_001::" - + DedupUtility.md5(bp.get().getOriginalID()); + + DHPUtils.md5(bp.get().getOriginalID()); } else { return bp.get().getOriginalID().split("\\|")[0] + "|" + createPrefix(bp.get().getPid().getQualifier().getClassid()) + "::" - + DedupUtility.md5(bp.get().getPid().getValue()); + + DHPUtils.md5(bp.get().getPid().getValue()); } } // pick the best pid from the entity. Returns a list (length 1) to save time in the call - public static List bestPidtoIdentifier(T entity) { + public static List bestPidToIdentifier(T entity) { if (entity.getPid() == null || entity.getPid().size() == 0) return Lists diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/Identifier.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/Identifier.java index 480b52341..65441c585 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/Identifier.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/Identifier.java @@ -4,6 +4,8 @@ package eu.dnetlib.dhp.oa.dedup; import java.io.Serializable; import java.util.Date; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import eu.dnetlib.dhp.schema.common.EntityType; import eu.dnetlib.dhp.schema.oaf.KeyValue; @@ -35,7 +37,7 @@ public class Identifier implements Serializable, Comparable { return pid; } - public void setPid(StructuredProperty pidValue) { + public void setPid(StructuredProperty pid) { this.pid = pid; } @@ -91,25 +93,29 @@ public class Identifier implements Serializable, Comparable { public int compareTo(Identifier i) { // priority in comparisons: 1) pidtype, 2) collectedfrom (depending on the entity type) , 3) date 4) // alphabetical order of the originalID + + Set lKeys = this.collectedFrom.stream().map(KeyValue::getKey).collect(Collectors.toSet()); + Set rKeys = i.getCollectedFrom().stream().map(KeyValue::getKey).collect(Collectors.toSet()); + if (this.getType().compareTo(i.getType()) == 0) { // same type if (entityType == EntityType.publication) { - if (isFromDatasourceID(this.collectedFrom, IdGenerator.CROSSREF_ID) - && !isFromDatasourceID(i.collectedFrom, IdGenerator.CROSSREF_ID)) + if (isFromDatasourceID(lKeys, IdGenerator.CROSSREF_ID) + && !isFromDatasourceID(rKeys, IdGenerator.CROSSREF_ID)) return 1; - if (isFromDatasourceID(i.collectedFrom, IdGenerator.CROSSREF_ID) - && !isFromDatasourceID(this.collectedFrom, IdGenerator.CROSSREF_ID)) + if (isFromDatasourceID(rKeys, IdGenerator.CROSSREF_ID) + && !isFromDatasourceID(lKeys, IdGenerator.CROSSREF_ID)) return -1; } if (entityType == EntityType.dataset) { - if (isFromDatasourceID(this.collectedFrom, IdGenerator.DATACITE_ID) - && !isFromDatasourceID(i.collectedFrom, IdGenerator.DATACITE_ID)) + if (isFromDatasourceID(lKeys, IdGenerator.DATACITE_ID) + && !isFromDatasourceID(rKeys, IdGenerator.DATACITE_ID)) return 1; - if (isFromDatasourceID(i.collectedFrom, IdGenerator.DATACITE_ID) - && !isFromDatasourceID(this.collectedFrom, IdGenerator.DATACITE_ID)) + if (isFromDatasourceID(rKeys, IdGenerator.DATACITE_ID) + && !isFromDatasourceID(lKeys, IdGenerator.DATACITE_ID)) return -1; } - if (this.getDate().compareTo(date) == 0) {// same date + if (this.getDate().compareTo(i.getDate()) == 0) {// same date if (this.originalID.compareTo(i.originalID) > 0) this.useOriginal = true; @@ -120,19 +126,14 @@ public class Identifier implements Serializable, Comparable { return -this.originalID.compareTo(i.originalID); } else // the minus is because we need to take the elder date - return -this.getDate().compareTo(date); + return -this.getDate().compareTo(i.getDate()); } else { return this.getType().compareTo(i.getType()); } } - public boolean isFromDatasourceID(List collectedFrom, String dsId) { - - for (KeyValue cf : collectedFrom) { - if (cf.getKey().equals(dsId)) - return true; - } - return false; + public boolean isFromDatasourceID(Set collectedFrom, String dsId) { + return collectedFrom.contains(dsId); } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/PidType.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/PidType.java index c3241bac6..d644e689b 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/PidType.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/PidType.java @@ -4,7 +4,7 @@ package eu.dnetlib.dhp.oa.dedup; public enum PidType { // from the less to the more important - undefined, original, orcid, ror, grid, pdb, arXiv, pmid, doi; + undefined, original, orcid, ror, grid, pdb, arXiv, pmid, pmc, doi; public static PidType classidValueOf(String s) { try { diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/graph/ConnectedComponent.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/graph/ConnectedComponent.java index cd4f99f63..3d0d24d23 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/graph/ConnectedComponent.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/graph/ConnectedComponent.java @@ -6,6 +6,7 @@ import java.io.Serializable; import java.util.Set; import java.util.stream.Collectors; +import eu.dnetlib.dhp.utils.DHPUtils; import org.apache.commons.lang.StringUtils; import org.codehaus.jackson.annotate.JsonIgnore; @@ -36,7 +37,7 @@ public class ConnectedComponent implements Serializable { if (docIds.size() > 1) { final String s = getMin(); String prefix = s.split("\\|")[0]; - ccId = prefix + "|dedup_wf_001::" + DedupUtility.md5(s); + ccId = prefix + "|dedup_wf_001::" + DHPUtils.md5(s); return ccId; } else { return docIds.iterator().next(); From 1804c5d809e6d72fd406151cfe6a1cd5d3808f4f Mon Sep 17 00:00:00 2001 From: miconis Date: Tue, 6 Oct 2020 16:44:51 +0200 Subject: [PATCH 009/445] refactoring: classes moved in the right package --- .../main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java | 1 + .../src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java | 3 ++- .../main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java | 1 + .../main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java | 1 + .../java/eu/dnetlib/dhp/oa/dedup/{ => model}/Identifier.java | 3 ++- .../java/eu/dnetlib/dhp/oa/dedup/{ => model}/OrgSimRel.java | 2 +- .../main/java/eu/dnetlib/dhp/oa/dedup/{ => model}/PidType.java | 2 +- 7 files changed, 9 insertions(+), 4 deletions(-) rename dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/{ => model}/Identifier.java (97%) rename dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/{ => model}/OrgSimRel.java (98%) rename dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/{ => model}/PidType.java (88%) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java index fd37b75ee..d488f4340 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java @@ -3,6 +3,7 @@ package eu.dnetlib.dhp.oa.dedup; import java.util.*; +import eu.dnetlib.dhp.oa.dedup.model.Identifier; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.api.java.function.MapGroupsFunction; import org.apache.spark.sql.Dataset; diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java index b2b81f4cb..2831ab5d0 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java @@ -6,8 +6,9 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; +import eu.dnetlib.dhp.oa.dedup.model.Identifier; +import eu.dnetlib.dhp.oa.dedup.model.PidType; import eu.dnetlib.dhp.utils.DHPUtils; -import org.apache.commons.lang.NullArgumentException; import org.apache.commons.lang.StringUtils; import com.google.common.collect.Lists; diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java index beb4dbad7..ea6e6db82 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.util.Optional; import java.util.Properties; +import eu.dnetlib.dhp.oa.dedup.model.OrgSimRel; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; import org.apache.spark.api.java.function.FilterFunction; diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java index 5e0081d72..4a3ce80df 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Optional; import java.util.Properties; +import eu.dnetlib.dhp.oa.dedup.model.OrgSimRel; import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/Identifier.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java similarity index 97% rename from dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/Identifier.java rename to dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java index 65441c585..30b1582f7 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/Identifier.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.oa.dedup; +package eu.dnetlib.dhp.oa.dedup.model; import java.io.Serializable; import java.util.Date; @@ -7,6 +7,7 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import eu.dnetlib.dhp.oa.dedup.IdGenerator; import eu.dnetlib.dhp.schema.common.EntityType; import eu.dnetlib.dhp.schema.oaf.KeyValue; import eu.dnetlib.dhp.schema.oaf.StructuredProperty; diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/OrgSimRel.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/OrgSimRel.java similarity index 98% rename from dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/OrgSimRel.java rename to dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/OrgSimRel.java index 84dfecd62..50164ce4c 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/OrgSimRel.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/OrgSimRel.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.oa.dedup; +package eu.dnetlib.dhp.oa.dedup.model; import java.io.Serializable; diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/PidType.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/PidType.java similarity index 88% rename from dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/PidType.java rename to dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/PidType.java index d644e689b..cb9b2bd15 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/PidType.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/PidType.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.oa.dedup; +package eu.dnetlib.dhp.oa.dedup.model; public enum PidType { From 1abcabb6e6240758748f0ba15a0eee9b1dd4efb1 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 6 Oct 2020 18:55:23 +0200 Subject: [PATCH 010/445] WIP stable ids: IdentifierFactory & unit test --- .../schema/oaf/utils/IdentifierFactory.java | 27 ++------ .../dhp/schema/oaf/utils/PidComparator.java | 62 ++++++++++++++----- .../dnetlib/dhp/schema/oaf/utils/PidType.java | 17 +++++ .../oaf/utils/IdentifierFactoryTest.java | 43 +++++++++++++ .../dhp/schema/oaf/utils/publication_3.json | 1 + .../dhp/schema/oaf/utils/publication_4.json | 1 + .../dhp/schema/oaf/utils/publication_5.json | 1 + .../dhp/schema/oaf/utils/publication_doi.json | 1 + .../dhp/schema/oaf/utils/publication_pmc.json | 1 + .../dhp/schema/oaf/utils/publication_urn.json | 1 + 10 files changed, 116 insertions(+), 39 deletions(-) create mode 100644 dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidType.java create mode 100644 dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java create mode 100644 dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_3.json create mode 100644 dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_4.json create mode 100644 dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_5.json create mode 100644 dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi.json create mode 100644 dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_pmc.json create mode 100644 dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_urn.json diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 02a946154..45e3f84b1 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -5,39 +5,20 @@ import eu.dnetlib.dhp.schema.oaf.OafEntity; import eu.dnetlib.dhp.schema.oaf.StructuredProperty; import eu.dnetlib.dhp.utils.DHPUtils; import org.apache.commons.lang.StringUtils; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.Serializable; -import java.util.HashSet; import java.util.Objects; -import java.util.Set; /** * Factory class for OpenAIRE identifiers in the Graph */ public class IdentifierFactory implements Serializable { - private static final Logger log = LoggerFactory.getLogger(IdentifierFactory.class); - public static final String ID_SEPARATOR = "::"; public static final String ID_PREFIX_SEPARATOR = "|"; public final static String ID_REGEX = "^[0-9][0-9]\\"+ID_PREFIX_SEPARATOR+".{12}"+ID_SEPARATOR+"[a-zA-Z0-9]{32}$"; public static final int ID_PREFIX_LEN = 12; - public static Set acceptedPidTypes = new HashSet<>(); - - static { - acceptedPidTypes.add("doi"); - acceptedPidTypes.add("doi"); - acceptedPidTypes.add("doi"); - acceptedPidTypes.add("doi"); - acceptedPidTypes.add("doi"); - acceptedPidTypes.add("doi"); - - } - public static String createIdentifier(T entity) { if (Objects.isNull(entity.getPid()) || entity.getPid().isEmpty()) { @@ -48,14 +29,14 @@ public class IdentifierFactory implements Serializable { .getPid() .stream() .filter(s -> Objects.nonNull(s.getQualifier())) - .filter(s -> acceptedPidTypes.contains(s.getQualifier().getClassid())) - .max(new PidComparator(entity)) + .filter(s -> PidType.isValid(s.getQualifier().getClassid())) + .min(new PidComparator<>(entity)) .map(s -> idFromPid(entity, s)) .map(IdentifierFactory::verifyIdSyntax) .orElseGet(entity::getId); } - protected static String verifyIdSyntax(String s) { + private static String verifyIdSyntax(String s) { if(StringUtils.isBlank(s) || !s.matches(ID_REGEX)) { throw new RuntimeException(String.format("malformed id: '%s'", s)); } else { @@ -74,7 +55,7 @@ public class IdentifierFactory implements Serializable { } private static String normalizePidValue(String value) { - //TODO more aggressive cleaning? keep only alphanum and punctation? + //TODO more aggressive cleaning? keep only alphanum and punctuation? return value.toLowerCase().replaceAll(" ", ""); } diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidComparator.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidComparator.java index 97bdd9c77..d0a8f87ce 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidComparator.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidComparator.java @@ -27,8 +27,8 @@ public class PidComparator implements Comparator implements Comparator Date: Wed, 7 Oct 2020 13:14:31 +0200 Subject: [PATCH 011/445] code formatting --- .../schema/oaf/utils/IdentifierFactory.java | 44 ++++++------- .../dnetlib/dhp/schema/oaf/utils/PidType.java | 15 ++--- .../oaf/utils/IdentifierFactoryTest.java | 62 ++++++++++--------- .../dhp/schema/oaf/H2020Programme.java | 1 - .../eu/dnetlib/dhp/schema/oaf/Result.java | 7 ++- .../project/PrepareProgramme.java | 10 ++- .../actionmanager/project/ProjectSubset.java | 1 - 7 files changed, 72 insertions(+), 68 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 45e3f84b1..183f68904 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -1,13 +1,14 @@ package eu.dnetlib.dhp.schema.oaf.utils; +import java.io.Serializable; +import java.util.Objects; + +import org.apache.commons.lang.StringUtils; + import eu.dnetlib.dhp.schema.oaf.OafEntity; import eu.dnetlib.dhp.schema.oaf.StructuredProperty; import eu.dnetlib.dhp.utils.DHPUtils; -import org.apache.commons.lang.StringUtils; - -import java.io.Serializable; -import java.util.Objects; /** * Factory class for OpenAIRE identifiers in the Graph @@ -16,7 +17,8 @@ public class IdentifierFactory implements Serializable { public static final String ID_SEPARATOR = "::"; public static final String ID_PREFIX_SEPARATOR = "|"; - public final static String ID_REGEX = "^[0-9][0-9]\\"+ID_PREFIX_SEPARATOR+".{12}"+ID_SEPARATOR+"[a-zA-Z0-9]{32}$"; + public final static String ID_REGEX = "^[0-9][0-9]\\" + ID_PREFIX_SEPARATOR + ".{12}" + ID_SEPARATOR + + "[a-zA-Z0-9]{32}$"; public static final int ID_PREFIX_LEN = 12; public static String createIdentifier(T entity) { @@ -26,18 +28,18 @@ public class IdentifierFactory implements Serializable { } return entity - .getPid() - .stream() - .filter(s -> Objects.nonNull(s.getQualifier())) - .filter(s -> PidType.isValid(s.getQualifier().getClassid())) - .min(new PidComparator<>(entity)) - .map(s -> idFromPid(entity, s)) - .map(IdentifierFactory::verifyIdSyntax) - .orElseGet(entity::getId); + .getPid() + .stream() + .filter(s -> Objects.nonNull(s.getQualifier())) + .filter(s -> PidType.isValid(s.getQualifier().getClassid())) + .min(new PidComparator<>(entity)) + .map(s -> idFromPid(entity, s)) + .map(IdentifierFactory::verifyIdSyntax) + .orElseGet(entity::getId); } private static String verifyIdSyntax(String s) { - if(StringUtils.isBlank(s) || !s.matches(ID_REGEX)) { + if (StringUtils.isBlank(s) || !s.matches(ID_REGEX)) { throw new RuntimeException(String.format("malformed id: '%s'", s)); } else { return s; @@ -46,16 +48,16 @@ public class IdentifierFactory implements Serializable { private static String idFromPid(T entity, StructuredProperty s) { return new StringBuilder() - .append(StringUtils.substringBefore(entity.getId(), ID_PREFIX_SEPARATOR)) - .append(ID_PREFIX_SEPARATOR) - .append(createPrefix(s.getQualifier().getClassid())) - .append(ID_SEPARATOR) - .append(DHPUtils.md5(normalizePidValue(s.getValue()))) - .toString(); + .append(StringUtils.substringBefore(entity.getId(), ID_PREFIX_SEPARATOR)) + .append(ID_PREFIX_SEPARATOR) + .append(createPrefix(s.getQualifier().getClassid())) + .append(ID_SEPARATOR) + .append(DHPUtils.md5(normalizePidValue(s.getValue()))) + .toString(); } private static String normalizePidValue(String value) { - //TODO more aggressive cleaning? keep only alphanum and punctuation? + // TODO more aggressive cleaning? keep only alphanum and punctuation? return value.toLowerCase().replaceAll(" ", ""); } diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidType.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidType.java index 87af06ec2..0e6a694de 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidType.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidType.java @@ -1,17 +1,18 @@ + package eu.dnetlib.dhp.schema.oaf.utils; import org.apache.commons.lang3.EnumUtils; public enum PidType { - // Result - doi, pmid, pmc, handle, arXiv, NCID, GBIF, nct, pdb, + // Result + doi, pmid, pmc, handle, arXiv, NCID, GBIF, nct, pdb, - // Organization + // Organization GRID, mag_id, urn; - public static boolean isValid(String type) { - return EnumUtils.isValidEnum(PidType.class, type); - } - + public static boolean isValid(String type) { + return EnumUtils.isValidEnum(PidType.class, type); + } + } diff --git a/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java index 2d34c58c8..d458c613e 100644 --- a/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java +++ b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java @@ -1,43 +1,47 @@ + package eu.dnetlib.dhp.schema.oaf.utils; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import eu.dnetlib.dhp.schema.oaf.Publication; -import eu.dnetlib.dhp.utils.DHPUtils; -import org.apache.commons.io.IOUtils; - -import org.junit.jupiter.api.Test; - -import java.io.IOException; - import static org.junit.jupiter.api.Assertions.*; +import java.io.IOException; + +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.dnetlib.dhp.schema.oaf.Publication; +import eu.dnetlib.dhp.utils.DHPUtils; + public class IdentifierFactoryTest { - private static ObjectMapper OBJECT_MAPPER = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + private static ObjectMapper OBJECT_MAPPER = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - @Test - public void testCreateIdentifierForPublication() throws IOException { + @Test + public void testCreateIdentifierForPublication() throws IOException { - verifyIdentifier("publication_doi.json", "50|doi_________::" + DHPUtils.md5("10.1016/j.cmet.2011.03.013")); - verifyIdentifier("publication_pmc.json", "50|pmc_________::" + DHPUtils.md5("21459329")); - verifyIdentifier("publication_urn.json", "50|urn_________::" + DHPUtils.md5("urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2")); + verifyIdentifier("publication_doi.json", "50|doi_________::" + DHPUtils.md5("10.1016/j.cmet.2011.03.013")); + verifyIdentifier("publication_pmc.json", "50|pmc_________::" + DHPUtils.md5("21459329")); + verifyIdentifier( + "publication_urn.json", + "50|urn_________::" + DHPUtils.md5("urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2")); - final String defaultID = "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f"; - verifyIdentifier("publication_3.json", defaultID); - verifyIdentifier("publication_4.json", defaultID); - verifyIdentifier("publication_5.json", defaultID); - } + final String defaultID = "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f"; + verifyIdentifier("publication_3.json", defaultID); + verifyIdentifier("publication_4.json", defaultID); + verifyIdentifier("publication_5.json", defaultID); + } - protected void verifyIdentifier(String filename, String expectedID) throws IOException { - final String json = IOUtils.toString(getClass().getResourceAsStream(filename)); - final Publication pub = OBJECT_MAPPER.readValue(json, Publication.class); + protected void verifyIdentifier(String filename, String expectedID) throws IOException { + final String json = IOUtils.toString(getClass().getResourceAsStream(filename)); + final Publication pub = OBJECT_MAPPER.readValue(json, Publication.class); - String id = IdentifierFactory.createIdentifier(pub); - - assertNotNull(id); - assertEquals(expectedID, id); - } + String id = IdentifierFactory.createIdentifier(pub); + assertNotNull(id); + assertEquals(expectedID, id); + } } diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/H2020Programme.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/H2020Programme.java index aacb228db..101d46d35 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/H2020Programme.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/H2020Programme.java @@ -10,7 +10,6 @@ import java.util.Objects; * - private String description to store the description of the programme */ - public class H2020Programme implements Serializable { private String code; private String description; diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Result.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Result.java index 73c5613ea..443c18230 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Result.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Result.java @@ -1,14 +1,14 @@ package eu.dnetlib.dhp.schema.oaf; -import eu.dnetlib.dhp.schema.common.LicenseComparator; - import java.io.Serializable; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; +import eu.dnetlib.dhp.schema.common.LicenseComparator; + public class Result extends OafEntity implements Serializable { private List measures; @@ -247,7 +247,8 @@ public class Result extends OafEntity implements Serializable { instance = mergeLists(instance, r.getInstance()); - if (r.getBestaccessright() != null && new LicenseComparator().compare(r.getBestaccessright(), bestaccessright) < 0) + if (r.getBestaccessright() != null + && new LicenseComparator().compare(r.getBestaccessright(), bestaccessright) < 0) bestaccessright = r.getBestaccessright(); if (r.getResulttype() != null && compareTrust(this, r) < 0) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/PrepareProgramme.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/PrepareProgramme.java index 2cf023fb9..7f0ca983f 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/PrepareProgramme.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/PrepareProgramme.java @@ -177,14 +177,12 @@ public class PrepareProgramme { prepareClassification(h2020Programmes); - h2020Programmes.map(csvProgramme -> OBJECT_MAPPER.writeValueAsString(csvProgramme)) - .saveAsTextFile(outputPath); - - + h2020Programmes + .map(csvProgramme -> OBJECT_MAPPER.writeValueAsString(csvProgramme)) + .saveAsTextFile(outputPath); } - private static void prepareClassification(JavaRDD h2020Programmes) { Object[] codedescription = h2020Programmes .map(value -> new Tuple2<>(value.getCode(), value.getTitle())) @@ -255,7 +253,7 @@ public class PrepareProgramme { } h2020Programmes.foreach(csvProgramme -> { if (!csvProgramme.getCode().endsWith(".") && !csvProgramme.getCode().contains("Euratom") - && !csvProgramme.getCode().equals("H2020-EC")) + && !csvProgramme.getCode().equals("H2020-EC")) csvProgramme.setClassification(map.get(csvProgramme.getCode() + ".")); else csvProgramme.setClassification(map.get(csvProgramme.getCode())); diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/ProjectSubset.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/ProjectSubset.java index c51c10876..06f8c2fef 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/ProjectSubset.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/ProjectSubset.java @@ -10,7 +10,6 @@ public class ProjectSubset implements Serializable { private String code; - public String getCode() { return code; } From 6f8720982c7f994f8dd86d1b9a905bae298d7146 Mon Sep 17 00:00:00 2001 From: miconis Date: Fri, 9 Oct 2020 09:30:23 +0200 Subject: [PATCH 012/445] bug fix in the idgenerator and test implementation --- .../dhp/oa/dedup/DedupRecordFactory.java | 1 - .../eu/dnetlib/dhp/oa/dedup/IdGenerator.java | 32 ++-- .../dhp/oa/dedup/model/Identifier.java | 2 +- .../dnetlib/dhp/oa/dedup/IdGeneratorTest.java | 140 ++++++++++++++++++ .../dedup/json/publication_idgeneration.json | 3 + 5 files changed, 165 insertions(+), 13 deletions(-) create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_idgeneration.json diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java index d488f4340..2e4b219b5 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java @@ -1,4 +1,3 @@ - package eu.dnetlib.dhp.oa.dedup; import java.util.*; diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java index 2831ab5d0..7980d8e69 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java @@ -24,6 +24,7 @@ public class IdGenerator implements Serializable { public static String CROSSREF_ID = "10|openaire____::081b82f96300b6a6e3d282bad31cb6e2"; public static String DATACITE_ID = "10|openaire____::9e3be59865b2c1c335d32dae2fe7b254"; + public static String BASE_DATE = "2000-01-01"; // pick the best pid from the list (consider date and pidtype) public static String generate(List pids, String defaultID) { @@ -45,14 +46,27 @@ public class IdGenerator implements Serializable { } + public static ArrayList createBasePid(T entity, SimpleDateFormat sdf) { + + Date date; + try { + date = sdf.parse(BASE_DATE); + } catch (ParseException e) { + date = new Date(); + } + return Lists + .newArrayList( + new Identifier(new StructuredProperty(), date, PidType.original, entity.getCollectedfrom(), + EntityType.fromClass(entity.getClass()), entity.getId())); + } + // pick the best pid from the entity. Returns a list (length 1) to save time in the call public static List bestPidToIdentifier(T entity) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + if (entity.getPid() == null || entity.getPid().size() == 0) - return Lists - .newArrayList( - new Identifier(new StructuredProperty(), new Date(), PidType.original, entity.getCollectedfrom(), - EntityType.fromClass(entity.getClass()), entity.getId())); + return createBasePid(entity, sdf); Optional bp = entity .getPid() @@ -64,14 +78,10 @@ public class IdGenerator implements Serializable { .map( structuredProperty -> Lists .newArrayList( - new Identifier(structuredProperty, extractDate(entity, new SimpleDateFormat("yyyy-MM-dd")), + new Identifier(structuredProperty, extractDate(entity, sdf), PidType.classidValueOf(structuredProperty.getQualifier().getClassid()), entity.getCollectedfrom(), EntityType.fromClass(entity.getClass()), entity.getId()))) - .orElseGet( - () -> Lists - .newArrayList( - new Identifier(new StructuredProperty(), new Date(), PidType.original, - entity.getCollectedfrom(), EntityType.fromClass(entity.getClass()), entity.getId()))); + .orElseGet(() -> createBasePid(entity, sdf)); } @@ -91,7 +101,7 @@ public class IdGenerator implements Serializable { // 00-01-01 public static Date extractDate(T duplicate, SimpleDateFormat sdf) { - String date = "2000-01-01"; + String date = BASE_DATE; if (ModelSupport.isSubClass(duplicate, Result.class)) { Result result = (Result) duplicate; if (isWellformed(result.getDateofacceptance())) { diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java index 30b1582f7..95fd21c64 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java @@ -118,7 +118,7 @@ public class Identifier implements Serializable, Comparable { if (this.getDate().compareTo(i.getDate()) == 0) {// same date - if (this.originalID.compareTo(i.originalID) > 0) + if (this.originalID.compareTo(i.originalID) < 0) this.useOriginal = true; else i.setUseOriginal(true); diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java new file mode 100644 index 000000000..1bf851527 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java @@ -0,0 +1,140 @@ +package eu.dnetlib.dhp.oa.dedup; + +import com.google.common.collect.Lists; +import eu.dnetlib.dhp.oa.dedup.model.Identifier; +import eu.dnetlib.dhp.oa.dedup.model.PidType; +import eu.dnetlib.dhp.schema.common.EntityType; +import eu.dnetlib.dhp.schema.oaf.KeyValue; +import eu.dnetlib.dhp.schema.oaf.Publication; +import eu.dnetlib.dhp.schema.oaf.Qualifier; +import eu.dnetlib.dhp.schema.oaf.StructuredProperty; +import eu.dnetlib.pace.util.MapDocumentUtil; +import org.codehaus.jackson.map.ObjectMapper; +import org.junit.jupiter.api.*; +import scala.Tuple2; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class IdGeneratorTest { + + private static List bestIds; + private static List> pubs; + + private static List bestIds2; + private static List bestIds3; + + private static String testEntityBasePath; + + private static SimpleDateFormat sdf; + private static Date baseDate; + + @BeforeAll + public static void setUp() throws Exception { + + sdf = new SimpleDateFormat("yyyy-MM-dd"); + baseDate = sdf.parse("2000-01-01"); + + bestIds = new ArrayList<>(); + bestIds2 = Lists.newArrayList( + new Identifier(pid("pid1", "original", "original"), baseDate, PidType.original, keyValue("key", "value"), EntityType.publication, "50|originalID1"), + new Identifier(pid("pid2", "original", "original"), baseDate, PidType.original, keyValue("key", "value"), EntityType.publication, "50|originalID2"), + new Identifier(pid("pid3", "original", "original"), baseDate, PidType.original, keyValue("key", "value"), EntityType.publication, "50|originalID3") + ); + bestIds3 = Lists.newArrayList( + new Identifier(pid("pid1", "original", "original"), baseDate, PidType.original, keyValue("key", "value"), EntityType.publication, "50|originalID1"), + new Identifier(pid("pid2", "doi", "doi"), baseDate, PidType.doi, keyValue("key", "value"), EntityType.publication, "50|originalID2"), + new Identifier(pid("pid3", "original", "original"), baseDate, PidType.original, keyValue("key", "value"), EntityType.publication, "50|originalID3") + ); + + testEntityBasePath = Paths + .get(SparkDedupTest.class.getResource("/eu/dnetlib/dhp/dedup/json").toURI()) + .toFile() + .getAbsolutePath(); + + pubs = readSample(testEntityBasePath + "/publication_idgeneration.json", Publication.class); + + } + + @Test + @Order(1) + public void bestPidToIdentifierTest(){ + + List typesForAssertions = Lists.newArrayList(PidType.pmc.toString(), PidType.doi.toString(), PidType.doi.toString()); + + for (Tuple2 pub : pubs) { + List ids = IdGenerator.bestPidToIdentifier(pub._2()); + assertEquals(typesForAssertions.get(pubs.indexOf(pub)), ids.get(0).getPid().getQualifier().getClassid()); + bestIds.addAll(ids); + } + } + + @Test + @Order(2) + public void generateIdTest1(){ + String id1 = IdGenerator.generate(bestIds, "50|defaultID"); + + assertEquals("50|dedup_doi___::84f2cc49e3af11f20952eae15cdae066", id1); + } + + @Test + public void generateIdTest2(){ + String id1 = IdGenerator.generate(bestIds2, "50|defaultID"); + String id2 = IdGenerator.generate(bestIds3, "50|defaultID"); + + assertEquals("50|dedup_wf_001::2c56cc1914bffdb30fdff354e0099612", id1); + assertEquals("50|dedup_doi___::128ead3ed8d9ecf262704b6fcf592b8d", id2); + } + + public static List> readSample(String path, Class clazz) { + List> res = new ArrayList<>(); + BufferedReader reader; + try { + reader = new BufferedReader(new FileReader(path)); + String line = reader.readLine(); + while (line != null) { + res + .add( + new Tuple2<>( + MapDocumentUtil.getJPathString("$.id", line), + new ObjectMapper().readValue(line, clazz))); + // read next line + line = reader.readLine(); + } + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + return res; + } + + public static StructuredProperty pid(String pid, String classid, String classname){ + + StructuredProperty sp = new StructuredProperty(); + sp.setValue(pid); + Qualifier q = new Qualifier(); + q.setSchemeid(classid); + q.setSchemename(classname); + q.setClassname(classname); + q.setClassid(classid); + sp.setQualifier(q); + return sp; + } + + public static List keyValue(String key, String value){ + + KeyValue kv = new KeyValue(); + kv.setKey(key); + kv.setValue(value); + return Lists.newArrayList(kv); + } +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_idgeneration.json b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_idgeneration.json new file mode 100644 index 000000000..b2300acc6 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_idgeneration.json @@ -0,0 +1,3 @@ +{"context": [], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:datasetarchive", "classname": "sysimport:crosswalk:datasetarchive", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "dedup-similarity-result-levenstein", "invisible": false, "trust": "0.95"}, "resourcetype": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "pid": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "arXiv", "classname": "arXiv", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "arXivSampleID"} ,{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "pmc", "classname": "pmc", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "pmcsampleid"}], "contributor": [], "resulttype": {"classid": "publication", "classname": "publication", "schemename": "dnet:result_typologies", "schemeid": "dnet:result_typologies"}, "relevantdate": [], "collectedfrom": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Journal.fi", "key": "10|openaire____::6eef8049d0feedc089ee009abca55e35"}], "id": "50|a89337edbe55::4930db9e954866d70916cbfba9f81f97", "subject": [], "instance": [{"refereed": null, "hostedby": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Journal.fi", "key": "10|openaire____::6eef8049d0feedc089ee009abca55e35"}, "processingchargeamount": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "license": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "url": [], "distributionlocation": "", "processingchargecurrency": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "collectedfrom": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Journal.fi", "key": "10|openaire____::6eef8049d0feedc089ee009abca55e35"}, "accessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "instancetype": {"classid": "0001", "classname": "Article", "schemename": "dnet:dataCite_resource", "schemeid": "dnet:dataCite_resource"}}], "embargoenddate": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "lastupdatetimestamp": 0, "author": [{"surname": "Go\\u0308tz", "name": "Julia", "pid": [], "rank": 5, "affiliation": [], "fullname": "G\\u00f6tz, Julia"}, {"surname": "Wolff", "name": "Stephan", "pid": [], "rank": 6, "affiliation": [], "fullname": "Wolff, Stephan"}, {"surname": "Jansen", "name": "Olav", "pid": [], "rank": 7, "affiliation": [], "fullname": "Jansen, Olav"}, {"surname": "Dressler", "name": "Dirk", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0000-0656-9999"},{"qualifier": {"classid": "id", "classname": "id"}, "value": "987654321"}], "rank": 8, "affiliation": [], "fullname": "Dressler, Dirk"}, {"surname": "Schneider", "name": "Susanne A.", "pid": [], "rank": 9, "affiliation": [], "fullname": "Schneider, Susanne A."}], "source": [], "dateofcollection": "2019-11-05T14:49:22.351Z", "fulltext": [], "dateoftransformation": "2019-11-05T16:10:58.988Z", "description": [], "format": [], "journal": {"issnPrinted": "1459-6067", "conferencedate": "", "conferenceplace": "", "name": "Agricultural and Food Science", "edition": "", "iss": "3", "sp": "", "vol": "27", "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "issnOnline": "1795-1895", "ep": "", "issnLinking": ""}, "coverage": [], "publisher": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "language": {"classid": "eng", "classname": "English", "schemename": "dnet:languages", "schemeid": "dnet:languages"}, "bestaccessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "country": [], "extraInfo": [], "originalId": [], "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2018-09-30"}, "title": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "main title", "classname": "main title", "schemename": "dnet:dataCite_title", "schemeid": "dnet:dataCite_title"}, "value": "Altered brain activation in a reversal learning task unmasks adaptive changes in cognitive control in writer's cramp"}]} +{"context": [], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:repository", "classname": "sysimport:crosswalk:repository", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": true, "inferenceprovenance": "dedup-similarity-result-levenstein", "invisible": false, "trust": "0.9"}, "resourcetype": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "pid": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "doi", "classname": "doi", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "10.1016/j.nicl.2015.11.006"}, {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "arXiv", "classname": "arXiv", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "arXivSampleID"} ,{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "pmc", "classname": "pmc", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "pmcsampleid"}], "contributor": [], "resulttype": {"classid": "publication", "classname": "publication", "schemename": "dnet:result_typologies", "schemeid": "dnet:result_typologies"}, "relevantdate": [], "collectedfrom": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "BASE (Open Access Aggregator)", "key": "10|openaire____::df45502607927471ecf8a6ae83683ff5"}], "id": "50|base_oa_____::0968af610a356656706657e4f234b340", "subject": [], "instance": [{"refereed": null, "hostedby": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "NeuroImage: Clinical", "key": "10|doajarticles::0c0e74daa5d95504eade9c81ebbd5b8a"}, "processingchargeamount": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "license": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "http://creativecommons.org/licenses/by-nc-nd/4.0/"}, "url": ["http://dx.doi.org/10.1016/j.nicl.2015.11.006"], "distributionlocation": "", "processingchargecurrency": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "collectedfrom": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "BASE (Open Access Aggregator)", "key": "10|openaire____::df45502607927471ecf8a6ae83683ff5"}, "accessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "instancetype": {"classid": "0001", "classname": "Article", "schemename": "dnet:publication_resource", "schemeid": "dnet:publication_resource"}}], "embargoenddate": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "lastupdatetimestamp": 0, "author": [{"surname": "Zeuner", "name": "Kirsten E.", "pid": [], "rank": 1, "affiliation": [], "fullname": "Zeuner, Kirsten E."}, {"surname": "Knutzen", "name": "Arne", "pid": [], "rank": 2, "affiliation": [], "fullname": "Knutzen, Arne"}, {"surname": "Granert", "name": "Oliver", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0002-0656-1023"}], "rank": 3, "affiliation": [], "fullname": "Granert, Oliver"}, {"surname": "Sablowsky", "name": "Simone", "pid": [], "rank": 4, "affiliation": [], "fullname": "Sablowsky, Simone"}, {"surname": "Go\\u0308tz", "name": "Julia", "pid": [], "rank": 5, "affiliation": [], "fullname": "G\\u00f6tz, Julia"}, {"surname": "Wolff", "name": "Stephan", "pid": [], "rank": 6, "affiliation": [], "fullname": "Wolff, Stephan"}, {"surname": "Jansen", "name": "Olav", "pid": [], "rank": 7, "affiliation": [], "fullname": "Jansen, Olav"}, {"surname": "Dressler", "name": "Dirk", "pid": [], "rank": 8, "affiliation": [], "fullname": "Dressler, Dirk"}, {"surname": "Schneider", "name": "Susanne A.", "pid": [], "rank": 9, "affiliation": [], "fullname": "Schneider, Susanne A."}, {"surname": "Klein", "name": "Christine", "pid": [], "rank": 10, "affiliation": [], "fullname": "Klein, Christine"}, {"surname": "Deuschl", "name": "Gu\\u0308nther", "pid": [], "rank": 11, "affiliation": [], "fullname": "Deuschl, G\\u00fcnther"}, {"surname": "Eimeren", "name": "Thilo", "pid": [], "rank": 12, "affiliation": [], "fullname": "van Eimeren, Thilo"}, {"surname": "Witt", "name": "Karsten", "pid": [], "rank": 13, "affiliation": [], "fullname": "Witt, Karsten"}], "source": [], "dateofcollection": "2017-07-27T19:04:09.131Z", "fulltext": [], "dateoftransformation": "2019-01-23T10:15:19.582Z", "description": [], "format": [], "journal": {"issnPrinted": "2213-1582", "conferencedate": "", "conferenceplace": "", "name": "NeuroImage: Clinical", "edition": "", "iss": "", "sp": "63", "vol": "10", "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "issnOnline": "", "ep": "70", "issnLinking": ""}, "coverage": [], "publisher": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Elsevier BV"}, "language": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "bestaccessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "country": [{"classid": "IT", "classname": "Italy", "schemeid": "dnet:countries", "schemename": "dnet:countries"}], "extraInfo": [], "originalId": ["10.1016/j.nicl.2015.11.006"], "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "title": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "main title", "classname": "main title", "schemename": "dnet:dataCite_title", "schemeid": "dnet:dataCite_title"}, "value": "Altered brain activation in a reversal learning task unmasks adaptive changes in cognitive control in writer's cramp"}]} +{"context": [], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:datasetarchive", "classname": "sysimport:crosswalk:datasetarchive", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": true, "inferenceprovenance": "dedup-similarity-result-levenstein", "invisible": false, "trust": "0.9"}, "resourcetype": {"classid": "0004", "classname": "Conference object", "schemename": "dnet:dataCite_resource", "schemeid": "dnet:dataCite_resource"}, "pid": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "arXiv", "classname": "arXiv", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "arXivSampleID"} ,{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "doi", "classname": "doi", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "10.0001/doi2"}], "contributor": [], "resulttype": {"classid": "publication", "classname": "publication", "schemename": "dnet:result_typologies", "schemeid": "dnet:result_typologies"}, "relevantdate": [], "collectedfrom": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "CRIS UNS (Current Research Information System University of Novi Sad)", "key": "10|CRIS_UNS____::f66f1bd369679b5b077dcdf006089556"}], "id": "50|CrisUnsNoviS::9f9d014eea45dab432cab636c4c9cf39", "subject": [], "instance": [{"refereed": null, "hostedby": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "CRIS UNS (Current Research Information System University of Novi Sad)", "key": "10|CRIS_UNS____::f66f1bd369679b5b077dcdf006089556"}, "processingchargeamount": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "license": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "url": ["https://www.cris.uns.ac.rs/record.jsf?recordId=113444&source=OpenAIRE&language=en"], "distributionlocation": "", "processingchargecurrency": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2019-01-01"}, "collectedfrom": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "CRIS UNS (Current Research Information System University of Novi Sad)", "key": "10|CRIS_UNS____::f66f1bd369679b5b077dcdf006089556"}, "accessright": {"classid": "UNKNOWN", "classname": "UNKNOWN", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "instancetype": {"classid": "0004", "classname": "Conference object", "schemename": "dnet:dataCite_resource", "schemeid": "dnet:dataCite_resource"}}], "embargoenddate": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "lastupdatetimestamp": 0, "author": [{"surname": "Zeuner", "name": "Kirsten E.", "pid": [], "rank": 1, "affiliation": [], "fullname": "Zeuner, Kirsten E."}, {"surname": "Knutzen", "name": "Arne", "pid": [], "rank": 2, "affiliation": [], "fullname": "Knutzen, Arne"}, {"surname": "Granert", "name": "Oliver", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0002-0656-1023"}, {"qualifier": {"classid": "pubmed", "classname": "pubmed"}, "value": "pubmed.it"}], "rank": 3, "affiliation": [], "fullname": "Granert, Oliver"}, {"surname": "Sablowsky", "name": "Simone", "pid": [{"qualifier": {"classid": "id", "classname": "id"}, "value": "12345678"}], "rank": 4, "affiliation": [], "fullname": "Sablowsky, Simone"}, {"surname": "Go\\u0308tz", "name": "Julia", "pid": [], "rank": 5, "affiliation": [], "fullname": "G\\u00f6tz, Julia"}, {"surname": "Wolff", "name": "Stephan", "pid": [], "rank": 6, "affiliation": [], "fullname": "Wolff, Stephan"}, {"surname": "Jansen", "name": "Olav", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0000-0656-1023"},{"qualifier": {"classid": "id", "classname": "id"}, "value": "987654321"}], "rank": 7, "affiliation": [], "fullname": "Jansen, Olav"}, {"surname": "Dressler", "name": "Dirk", "pid": [], "rank": 8, "affiliation": [], "fullname": "Dressler, Dirk"}, {"surname": "Schneider", "name": "Susanne A.", "pid": [], "rank": 9, "affiliation": [], "fullname": "Schneider, Susanne A."}], "source": [], "dateofcollection": "2020-03-10T15:05:38.685Z", "fulltext": [], "dateoftransformation": "2020-03-11T20:11:13.15Z", "description": [], "format": [], "journal": {"issnPrinted": "", "conferencedate": "", "conferenceplace": "", "name": "", "edition": "", "iss": "", "sp": "", "vol": "", "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "issnOnline": "", "ep": "", "issnLinking": ""}, "coverage": [], "publisher": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "language": {"classid": "en", "classname": "en", "schemename": "dnet:languages", "schemeid": "dnet:languages"}, "bestaccessright": {"classid": "UNKNOWN", "classname": "not available", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "country": [{"classid": "FI", "classname": "Finland", "schemeid": "dnet:countries", "schemename": "dnet:countries"}], "extraInfo": [], "originalId": ["(BISIS)113444", "https://www.cris.uns.ac.rs/record.jsf?recordId=113444&source=OpenAIRE&language=en"], "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "title": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "test title", "classname": "test title", "schemename": "dnet:dataCite_title", "schemeid": "dnet:dataCite_title"}, "value": "Antichains of copies of ultrahomogeneous structures"}]} \ No newline at end of file From 34f1d0904b6f2142e32fe4b3dafddcd390424779 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Fri, 16 Oct 2020 16:00:19 +0200 Subject: [PATCH 013/445] common IdentifierFactory in use on the mapping from the aggregator data --- .../raw/AbstractMdRecordToOafMapper.java | 71 +++++++++---------- .../raw/MigrateDbEntitiesApplication.java | 30 ++++---- .../dhp/oa/graph/raw/OafToOafMapper.java | 24 ++----- .../dhp/oa/graph/raw/OdfToOafMapper.java | 37 ++++------ .../dnetlib/dhp/oa/graph/raw/MappersTest.java | 40 ++++------- 5 files changed, 82 insertions(+), 120 deletions(-) diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java index 5b6ae72f1..50fc89f04 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java @@ -6,6 +6,8 @@ import static eu.dnetlib.dhp.schema.common.ModelConstants.*; import java.util.*; +import com.google.common.collect.Lists; +import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory; import org.apache.commons.lang3.StringUtils; import org.dom4j.Document; import org.dom4j.DocumentFactory; @@ -133,20 +135,33 @@ public abstract class AbstractMdRecordToOafMapper { final DataInfo info, final long lastUpdateTimestamp) { - final List oafs = new ArrayList<>(); + final OafEntity entity = createEntity(doc, type, instances, collectedFrom, info, lastUpdateTimestamp); + final String id = IdentifierFactory.createIdentifier(entity); + if (!id.equals(entity.getId())) { + entity.getOriginalId().add(entity.getId()); + entity.setId(id); + } + final List oafs = Lists.newArrayList(entity); + + if (!oafs.isEmpty()) { + oafs.addAll(addProjectRels(doc, entity)); + oafs.addAll(addOtherResultRels(doc, entity)); + } + + return oafs; + } + + private OafEntity createEntity(Document doc, String type, List instances, KeyValue collectedFrom, DataInfo info, long lastUpdateTimestamp) { switch (type.toLowerCase()) { case "publication": final Publication p = new Publication(); populateResultFields(p, doc, instances, collectedFrom, info, lastUpdateTimestamp); - p.setResulttype(PUBLICATION_DEFAULT_RESULTTYPE); p.setJournal(prepareJournal(doc, info)); - oafs.add(p); - break; + return p; case "dataset": final Dataset d = new Dataset(); populateResultFields(d, doc, instances, collectedFrom, info, lastUpdateTimestamp); - d.setResulttype(DATASET_DEFAULT_RESULTTYPE); d.setStoragedate(prepareDatasetStorageDate(doc, info)); d.setDevice(prepareDatasetDevice(doc, info)); d.setSize(prepareDatasetSize(doc, info)); @@ -154,48 +169,34 @@ public abstract class AbstractMdRecordToOafMapper { d.setLastmetadataupdate(prepareDatasetLastMetadataUpdate(doc, info)); d.setMetadataversionnumber(prepareDatasetMetadataVersionNumber(doc, info)); d.setGeolocation(prepareDatasetGeoLocations(doc, info)); - oafs.add(d); - break; + return d; case "software": final Software s = new Software(); populateResultFields(s, doc, instances, collectedFrom, info, lastUpdateTimestamp); - s.setResulttype(SOFTWARE_DEFAULT_RESULTTYPE); s.setDocumentationUrl(prepareSoftwareDocumentationUrls(doc, info)); s.setLicense(prepareSoftwareLicenses(doc, info)); s.setCodeRepositoryUrl(prepareSoftwareCodeRepositoryUrl(doc, info)); s.setProgrammingLanguage(prepareSoftwareProgrammingLanguage(doc, info)); - oafs.add(s); - break; + return s; case "": case "otherresearchproducts": default: final OtherResearchProduct o = new OtherResearchProduct(); populateResultFields(o, doc, instances, collectedFrom, info, lastUpdateTimestamp); - o.setResulttype(ORP_DEFAULT_RESULTTYPE); o.setContactperson(prepareOtherResearchProductContactPersons(doc, info)); o.setContactgroup(prepareOtherResearchProductContactGroups(doc, info)); o.setTool(prepareOtherResearchProductTools(doc, info)); - oafs.add(o); - break; + return o; } - - if (!oafs.isEmpty()) { - oafs.addAll(addProjectRels(doc, collectedFrom, info, lastUpdateTimestamp)); - oafs.addAll(addOtherResultRels(doc, collectedFrom, info, lastUpdateTimestamp)); - } - - return oafs; } private List addProjectRels( final Document doc, - final KeyValue collectedFrom, - final DataInfo info, - final long lastUpdateTimestamp) { + final OafEntity entity) { final List res = new ArrayList<>(); - final String docId = createOpenaireId(50, doc.valueOf("//dri:objIdentifier"), false); + final String docId = entity.getId(); for (final Object o : doc.selectNodes("//oaf:projectid")) { @@ -207,13 +208,11 @@ public abstract class AbstractMdRecordToOafMapper { res .add( getRelation( - docId, projectId, RESULT_PROJECT, OUTCOME, IS_PRODUCED_BY, collectedFrom, info, - lastUpdateTimestamp)); + docId, projectId, RESULT_PROJECT, OUTCOME, IS_PRODUCED_BY, entity)); res .add( getRelation( - projectId, docId, RESULT_PROJECT, OUTCOME, PRODUCES, collectedFrom, info, - lastUpdateTimestamp)); + projectId, docId, RESULT_PROJECT, OUTCOME, PRODUCES, entity)); } } @@ -225,26 +224,22 @@ public abstract class AbstractMdRecordToOafMapper { final String relType, final String subRelType, final String relClass, - final KeyValue collectedFrom, - final DataInfo info, - final long lastUpdateTimestamp) { + final OafEntity entity) { final Relation rel = new Relation(); rel.setRelType(relType); rel.setSubRelType(subRelType); rel.setRelClass(relClass); rel.setSource(source); rel.setTarget(target); - rel.setCollectedfrom(Arrays.asList(collectedFrom)); - rel.setDataInfo(info); - rel.setLastupdatetimestamp(lastUpdateTimestamp); + rel.setCollectedfrom(entity.getCollectedfrom()); + rel.setDataInfo(entity.getDataInfo()); + rel.setLastupdatetimestamp(entity.getLastupdatetimestamp()); return rel; } protected abstract List addOtherResultRels( final Document doc, - final KeyValue collectedFrom, - final DataInfo info, - final long lastUpdateTimestamp); + final OafEntity entity); private void populateResultFields( final Result r, @@ -257,7 +252,7 @@ public abstract class AbstractMdRecordToOafMapper { r.setLastupdatetimestamp(lastUpdateTimestamp); r.setId(createOpenaireId(50, doc.valueOf("//dri:objIdentifier"), false)); - r.setOriginalId(Arrays.asList(findOriginalId(doc))); + r.setOriginalId(Lists.newArrayList(findOriginalId(doc))); r.setCollectedfrom(Arrays.asList(collectedFrom)); r.setPid(prepareResultPids(doc, info)); diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java index adf7b92be..30fdd17e9 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java @@ -445,22 +445,26 @@ public class MigrateDbEntitiesApplication extends AbstractMigrationApplication i createOpenaireId(10, "infrastruct_::openaire", true), "OpenAIRE"); try { + final String targetType = rs.getString(TARGET_TYPE); if (rs.getString(SOURCE_TYPE).equals("context")) { final Result r; - if (rs.getString(TARGET_TYPE).equals("dataset")) { - r = new Dataset(); - r.setResulttype(DATASET_DEFAULT_RESULTTYPE); - } else if (rs.getString(TARGET_TYPE).equals("software")) { - r = new Software(); - r.setResulttype(SOFTWARE_DEFAULT_RESULTTYPE); - } else if (rs.getString(TARGET_TYPE).equals("other")) { - r = new OtherResearchProduct(); - r.setResulttype(ORP_DEFAULT_RESULTTYPE); - } else { - r = new Publication(); - r.setResulttype(PUBLICATION_DEFAULT_RESULTTYPE); + switch (targetType) { + case "dataset": + r = new Dataset(); + break; + case "software": + r = new Software(); + break; + case "other": + r = new OtherResearchProduct(); + break; + case "publication": + default: + r = new Publication(); + break; } + r.setId(createOpenaireId(50, rs.getString("target_id"), false)); r.setLastupdatetimestamp(lastUpdateTimestamp); r.setContext(prepareContext(rs.getString("source_id"), info)); @@ -470,7 +474,7 @@ public class MigrateDbEntitiesApplication extends AbstractMigrationApplication i return Arrays.asList(r); } else { final String sourceId = createOpenaireId(rs.getString(SOURCE_TYPE), rs.getString("source_id"), false); - final String targetId = createOpenaireId(rs.getString(TARGET_TYPE), rs.getString("target_id"), false); + final String targetId = createOpenaireId(targetType, rs.getString("target_id"), false); final Relation r1 = new Relation(); final Relation r2 = new Relation(); diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java index dea80fabd..135fcc595 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import eu.dnetlib.dhp.schema.oaf.*; import org.apache.commons.lang3.StringUtils; import org.dom4j.Document; import org.dom4j.Element; @@ -19,15 +20,6 @@ import com.google.common.collect.Lists; import eu.dnetlib.dhp.common.PacePerson; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; -import eu.dnetlib.dhp.schema.oaf.Author; -import eu.dnetlib.dhp.schema.oaf.DataInfo; -import eu.dnetlib.dhp.schema.oaf.Field; -import eu.dnetlib.dhp.schema.oaf.GeoLocation; -import eu.dnetlib.dhp.schema.oaf.Instance; -import eu.dnetlib.dhp.schema.oaf.KeyValue; -import eu.dnetlib.dhp.schema.oaf.Oaf; -import eu.dnetlib.dhp.schema.oaf.Qualifier; -import eu.dnetlib.dhp.schema.oaf.StructuredProperty; public class OafToOafMapper extends AbstractMdRecordToOafMapper { @@ -256,12 +248,10 @@ public class OafToOafMapper extends AbstractMdRecordToOafMapper { @Override protected List addOtherResultRels( - final Document doc, - final KeyValue collectedFrom, - final DataInfo info, - final long lastUpdateTimestamp) { - final String docId = createOpenaireId(50, doc.valueOf("//dri:objIdentifier"), false); + final Document doc, + final OafEntity entity) { + final String docId = entity.getId(); final List res = new ArrayList<>(); for (final Object o : doc.selectNodes("//*[local-name()='relatedDataset']")) { @@ -275,13 +265,11 @@ public class OafToOafMapper extends AbstractMdRecordToOafMapper { res .add( getRelation( - docId, otherId, RESULT_RESULT, RELATIONSHIP, IS_RELATED_TO, collectedFrom, info, - lastUpdateTimestamp)); + docId, otherId, RESULT_RESULT, RELATIONSHIP, IS_RELATED_TO, entity)); res .add( getRelation( - otherId, docId, RESULT_RESULT, RELATIONSHIP, IS_RELATED_TO, collectedFrom, info, - lastUpdateTimestamp)); + otherId, docId, RESULT_RESULT, RELATIONSHIP, IS_RELATED_TO, entity)); } } return res; diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java index 6fe7bb971..522b3f247 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java @@ -12,6 +12,8 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import eu.dnetlib.dhp.schema.common.ModelConstants; +import eu.dnetlib.dhp.schema.oaf.*; import org.apache.commons.lang3.StringUtils; import org.dom4j.Document; import org.dom4j.Node; @@ -20,15 +22,6 @@ import com.google.common.collect.Lists; import eu.dnetlib.dhp.common.PacePerson; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; -import eu.dnetlib.dhp.schema.oaf.Author; -import eu.dnetlib.dhp.schema.oaf.DataInfo; -import eu.dnetlib.dhp.schema.oaf.Field; -import eu.dnetlib.dhp.schema.oaf.GeoLocation; -import eu.dnetlib.dhp.schema.oaf.Instance; -import eu.dnetlib.dhp.schema.oaf.KeyValue; -import eu.dnetlib.dhp.schema.oaf.Oaf; -import eu.dnetlib.dhp.schema.oaf.Qualifier; -import eu.dnetlib.dhp.schema.oaf.StructuredProperty; public class OdfToOafMapper extends AbstractMdRecordToOafMapper { @@ -313,12 +306,10 @@ public class OdfToOafMapper extends AbstractMdRecordToOafMapper { @Override protected List addOtherResultRels( - final Document doc, - final KeyValue collectedFrom, - final DataInfo info, - final long lastUpdateTimestamp) { + final Document doc, + final OafEntity entity) { - final String docId = createOpenaireId(50, doc.valueOf("//dri:objIdentifier"), false); + final String docId = entity.getId(); final List res = new ArrayList<>(); @@ -330,30 +321,26 @@ public class OdfToOafMapper extends AbstractMdRecordToOafMapper { final String otherId = createOpenaireId(50, originalId, false); final String type = ((Node) o).valueOf("@relationType"); - if (type.equalsIgnoreCase("IsSupplementTo")) { + if (type.equalsIgnoreCase(IS_SUPPLEMENT_TO)) { res .add( getRelation( - docId, otherId, RESULT_RESULT, SUPPLEMENT, IS_SUPPLEMENT_TO, collectedFrom, info, - lastUpdateTimestamp)); + docId, otherId, RESULT_RESULT, SUPPLEMENT, IS_SUPPLEMENT_TO, entity)); res .add( getRelation( - otherId, docId, RESULT_RESULT, SUPPLEMENT, IS_SUPPLEMENTED_BY, collectedFrom, info, - lastUpdateTimestamp)); - } else if (type.equals("IsPartOf")) { - + otherId, docId, RESULT_RESULT, SUPPLEMENT, IS_SUPPLEMENTED_BY, entity)); + } else if (type.equalsIgnoreCase(IS_PART_OF)) { res .add( getRelation( - docId, otherId, RESULT_RESULT, PART, IS_PART_OF, collectedFrom, info, - lastUpdateTimestamp)); + docId, otherId, RESULT_RESULT, PART, IS_PART_OF, entity)); res .add( getRelation( - otherId, docId, RESULT_RESULT, PART, HAS_PARTS, collectedFrom, info, - lastUpdateTimestamp)); + otherId, docId, RESULT_RESULT, PART, HAS_PARTS, entity)); } else { + // TODO catch more semantics } } } diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java index 2c10f8f58..2f5466d49 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java @@ -11,6 +11,8 @@ import java.io.IOException; import java.util.List; import java.util.Optional; +import eu.dnetlib.dhp.schema.oaf.*; +import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.BeforeEach; @@ -24,14 +26,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.oa.graph.clean.CleaningFunctionTest; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; import eu.dnetlib.dhp.schema.common.ModelConstants; -import eu.dnetlib.dhp.schema.oaf.Author; -import eu.dnetlib.dhp.schema.oaf.Dataset; -import eu.dnetlib.dhp.schema.oaf.Field; -import eu.dnetlib.dhp.schema.oaf.Oaf; -import eu.dnetlib.dhp.schema.oaf.Publication; -import eu.dnetlib.dhp.schema.oaf.Relation; -import eu.dnetlib.dhp.schema.oaf.Software; -import eu.dnetlib.dhp.schema.oaf.StructuredProperty; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; @ExtendWith(MockitoExtension.class) @@ -71,7 +65,7 @@ public class MappersTest { assertValidId(p.getId()); - assertTrue(p.getOriginalId().size() == 1); + assertTrue(p.getOriginalId().size() == 2); assertEquals("10.3897/oneeco.2.e13718", p.getOriginalId().get(0)); assertValidId(p.getCollectedfrom().get(0).getKey()); @@ -123,22 +117,7 @@ public class MappersTest { assertNotNull(p.getBestaccessright()); assertEquals("OPEN", p.getBestaccessright().getClassid()); - assertValidId(r1.getSource()); - assertValidId(r1.getTarget()); - assertValidId(r2.getSource()); - assertValidId(r2.getTarget()); - assertValidId(r1.getCollectedfrom().get(0).getKey()); - assertValidId(r2.getCollectedfrom().get(0).getKey()); - assertNotNull(r1.getDataInfo()); - assertNotNull(r2.getDataInfo()); - assertNotNull(r1.getDataInfo().getTrust()); - assertNotNull(r2.getDataInfo().getTrust()); - assertEquals(r1.getSource(), r2.getTarget()); - assertEquals(r2.getSource(), r1.getTarget()); - assertTrue(StringUtils.isNotBlank(r1.getRelClass())); - assertTrue(StringUtils.isNotBlank(r2.getRelClass())); - assertTrue(StringUtils.isNotBlank(r1.getRelType())); - assertTrue(StringUtils.isNotBlank(r2.getRelType())); + verifyRelations(p, r1, r2); // System.out.println(new ObjectMapper().writeValueAsString(p)); // System.out.println(new ObjectMapper().writeValueAsString(r1)); @@ -177,7 +156,7 @@ public class MappersTest { final Relation r2 = (Relation) list.get(2); assertValidId(d.getId()); - assertTrue(d.getOriginalId().size() == 1); + assertTrue(d.getOriginalId().size() == 2); assertEquals("oai:zenodo.org:3234526", d.getOriginalId().get(0)); assertValidId(d.getCollectedfrom().get(0).getKey()); assertTrue(StringUtils.isNotBlank(d.getTitle().get(0).getValue())); @@ -230,10 +209,19 @@ public class MappersTest { }); assertEquals("0001", d.getInstance().get(0).getRefereed().getClassid()); + verifyRelations(d, r1, r2); + } + + private void verifyRelations(OafEntity e, Relation r1, Relation r2) { + assertEquals(e.getId(), r1.getSource()); + assertEquals(e.getId(), r2.getTarget()); + assertValidId(r1.getSource()); assertValidId(r1.getTarget()); assertValidId(r2.getSource()); assertValidId(r2.getTarget()); + assertValidId(r1.getCollectedfrom().get(0).getKey()); + assertValidId(r2.getCollectedfrom().get(0).getKey()); assertNotNull(r1.getDataInfo()); assertNotNull(r2.getDataInfo()); assertNotNull(r1.getDataInfo().getTrust()); From 266bf1a221e622479711a3ae95770c63c28eeed9 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Fri, 16 Oct 2020 17:02:10 +0200 Subject: [PATCH 014/445] common IdentifierFactory in use on the mapping from the aggregator data; merge the entities sharing the same id; code formatting --- .../raw/AbstractMdRecordToOafMapper.java | 8 +++-- .../raw/GenerateEntitiesApplication.java | 33 ++++++++++++------- .../dhp/oa/graph/raw/OafToOafMapper.java | 6 ++-- .../dhp/oa/graph/raw/OdfToOafMapper.java | 8 ++--- .../dnetlib/dhp/oa/graph/raw/MappersTest.java | 4 +-- 5 files changed, 36 insertions(+), 23 deletions(-) diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java index 50fc89f04..37c479e5f 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java @@ -6,18 +6,19 @@ import static eu.dnetlib.dhp.schema.common.ModelConstants.*; import java.util.*; -import com.google.common.collect.Lists; -import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory; import org.apache.commons.lang3.StringUtils; import org.dom4j.Document; import org.dom4j.DocumentFactory; import org.dom4j.DocumentHelper; import org.dom4j.Node; +import com.google.common.collect.Lists; + import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; import eu.dnetlib.dhp.schema.common.LicenseComparator; import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.*; +import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory; public abstract class AbstractMdRecordToOafMapper { @@ -152,7 +153,8 @@ public abstract class AbstractMdRecordToOafMapper { return oafs; } - private OafEntity createEntity(Document doc, String type, List instances, KeyValue collectedFrom, DataInfo info, long lastUpdateTimestamp) { + private OafEntity createEntity(Document doc, String type, List instances, KeyValue collectedFrom, + DataInfo info, long lastUpdateTimestamp) { switch (type.toLowerCase()) { case "publication": final Publication p = new Publication(); diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java index 3568dc52a..7b13e500e 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java @@ -29,16 +29,7 @@ import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.common.HdfsSupport; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; import eu.dnetlib.dhp.schema.common.ModelSupport; -import eu.dnetlib.dhp.schema.oaf.Dataset; -import eu.dnetlib.dhp.schema.oaf.Datasource; -import eu.dnetlib.dhp.schema.oaf.Oaf; -import eu.dnetlib.dhp.schema.oaf.OafEntity; -import eu.dnetlib.dhp.schema.oaf.Organization; -import eu.dnetlib.dhp.schema.oaf.OtherResearchProduct; -import eu.dnetlib.dhp.schema.oaf.Project; -import eu.dnetlib.dhp.schema.oaf.Publication; -import eu.dnetlib.dhp.schema.oaf.Relation; -import eu.dnetlib.dhp.schema.oaf.Software; +import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; import scala.Tuple2; @@ -124,7 +115,27 @@ public class GenerateEntitiesApplication { private static Oaf merge(final Oaf o1, final Oaf o2) { if (ModelSupport.isSubClass(o1, OafEntity.class)) { - ((OafEntity) o1).mergeFrom((OafEntity) o2); + if (ModelSupport.isSubClass(o1, Result.class)) { + if (ModelSupport.isSubClass(o1, Publication.class)) { + ((Publication) o1).mergeFrom((Publication) o2); + } else if (ModelSupport.isSubClass(o1, Dataset.class)) { + ((Dataset) o1).mergeFrom((Dataset) o2); + } else if (ModelSupport.isSubClass(o1, Software.class)) { + ((Software) o1).mergeFrom((Software) o2); + } else if (ModelSupport.isSubClass(o1, OtherResearchProduct.class)) { + ((OtherResearchProduct) o1).mergeFrom((OtherResearchProduct) o2); + } else { + throw new RuntimeException("invalid Result subtype:" + o1.getClass().getCanonicalName()); + } + } else if (ModelSupport.isSubClass(o1, Datasource.class)) { + ((Datasource) o1).mergeFrom((Datasource) o2); + } else if (ModelSupport.isSubClass(o1, Organization.class)) { + ((Organization) o1).mergeFrom((Organization) o2); + } else if (ModelSupport.isSubClass(o1, Project.class)) { + ((Project) o1).mergeFrom((Project) o2); + } else { + throw new RuntimeException("invalid OafEntity subtype:" + o1.getClass().getCanonicalName()); + } } else if (ModelSupport.isSubClass(o1, Relation.class)) { ((Relation) o1).mergeFrom((Relation) o2); } else { diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java index 135fcc595..543d22eeb 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java @@ -10,7 +10,6 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; -import eu.dnetlib.dhp.schema.oaf.*; import org.apache.commons.lang3.StringUtils; import org.dom4j.Document; import org.dom4j.Element; @@ -20,6 +19,7 @@ import com.google.common.collect.Lists; import eu.dnetlib.dhp.common.PacePerson; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; +import eu.dnetlib.dhp.schema.oaf.*; public class OafToOafMapper extends AbstractMdRecordToOafMapper { @@ -248,8 +248,8 @@ public class OafToOafMapper extends AbstractMdRecordToOafMapper { @Override protected List addOtherResultRels( - final Document doc, - final OafEntity entity) { + final Document doc, + final OafEntity entity) { final String docId = entity.getId(); final List res = new ArrayList<>(); diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java index 522b3f247..0319cb89e 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java @@ -12,8 +12,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import eu.dnetlib.dhp.schema.common.ModelConstants; -import eu.dnetlib.dhp.schema.oaf.*; import org.apache.commons.lang3.StringUtils; import org.dom4j.Document; import org.dom4j.Node; @@ -22,6 +20,8 @@ import com.google.common.collect.Lists; import eu.dnetlib.dhp.common.PacePerson; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; +import eu.dnetlib.dhp.schema.common.ModelConstants; +import eu.dnetlib.dhp.schema.oaf.*; public class OdfToOafMapper extends AbstractMdRecordToOafMapper { @@ -306,8 +306,8 @@ public class OdfToOafMapper extends AbstractMdRecordToOafMapper { @Override protected List addOtherResultRels( - final Document doc, - final OafEntity entity) { + final Document doc, + final OafEntity entity) { final String docId = entity.getId(); diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java index 2f5466d49..788ac09bc 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java @@ -11,8 +11,6 @@ import java.io.IOException; import java.util.List; import java.util.Optional; -import eu.dnetlib.dhp.schema.oaf.*; -import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.BeforeEach; @@ -26,6 +24,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.oa.graph.clean.CleaningFunctionTest; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; import eu.dnetlib.dhp.schema.common.ModelConstants; +import eu.dnetlib.dhp.schema.oaf.*; +import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; @ExtendWith(MockitoExtension.class) From 0e54803177cdaeba475f54e28145205a52f11525 Mon Sep 17 00:00:00 2001 From: miconis Date: Tue, 20 Oct 2020 12:19:46 +0200 Subject: [PATCH 015/445] bug fix in the id generator and implementation of jobs for organization dedup --- .../dhp/oa/dedup/DedupRecordFactory.java | 3 +- .../eu/dnetlib/dhp/oa/dedup/IdGenerator.java | 14 +- .../dhp/oa/dedup/SparkPrepareNewOrgs.java | 30 ++- .../dhp/oa/dedup/SparkPrepareOrgRels.java | 163 +++++++++---- .../oa/dedup/graph/ConnectedComponent.java | 2 +- .../dhp/oa/dedup/model/Identifier.java | 10 +- .../dnetlib/dhp/oa/dedup/model/OrgSimRel.java | 12 +- .../oa/dedup/orgsdedup/oozie_app/workflow.xml | 33 ++- .../oa/dedup/prepareNewOrgs_parameters.json | 6 + .../oa/dedup/prepareOrgRels_parameters.json | 6 - .../dnetlib/dhp/oa/dedup/IdGeneratorTest.java | 221 ++++++++++-------- .../dnetlib/dhp/oa/dedup/SparkDedupTest.java | 42 ++-- 12 files changed, 353 insertions(+), 189 deletions(-) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java index 2e4b219b5..eec36bc0e 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java @@ -1,8 +1,8 @@ + package eu.dnetlib.dhp.oa.dedup; import java.util.*; -import eu.dnetlib.dhp.oa.dedup.model.Identifier; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.api.java.function.MapGroupsFunction; import org.apache.spark.sql.Dataset; @@ -15,6 +15,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; +import eu.dnetlib.dhp.oa.dedup.model.Identifier; import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.schema.oaf.*; import scala.Tuple2; diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java index 7980d8e69..405a9abf1 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java @@ -6,25 +6,25 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; -import eu.dnetlib.dhp.oa.dedup.model.Identifier; -import eu.dnetlib.dhp.oa.dedup.model.PidType; -import eu.dnetlib.dhp.utils.DHPUtils; import org.apache.commons.lang.StringUtils; import com.google.common.collect.Lists; +import eu.dnetlib.dhp.oa.dedup.model.Identifier; +import eu.dnetlib.dhp.oa.dedup.model.PidType; import eu.dnetlib.dhp.schema.common.EntityType; import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.schema.oaf.Field; import eu.dnetlib.dhp.schema.oaf.OafEntity; import eu.dnetlib.dhp.schema.oaf.Result; import eu.dnetlib.dhp.schema.oaf.StructuredProperty; +import eu.dnetlib.dhp.utils.DHPUtils; public class IdGenerator implements Serializable { public static String CROSSREF_ID = "10|openaire____::081b82f96300b6a6e3d282bad31cb6e2"; public static String DATACITE_ID = "10|openaire____::9e3be59865b2c1c335d32dae2fe7b254"; - public static String BASE_DATE = "2000-01-01"; + public static String BASE_DATE = "2000-01-01"; // pick the best pid from the list (consider date and pidtype) public static String generate(List pids, String defaultID) { @@ -55,9 +55,9 @@ public class IdGenerator implements Serializable { date = new Date(); } return Lists - .newArrayList( - new Identifier(new StructuredProperty(), date, PidType.original, entity.getCollectedfrom(), - EntityType.fromClass(entity.getClass()), entity.getId())); + .newArrayList( + new Identifier(new StructuredProperty(), date, PidType.original, entity.getCollectedfrom(), + EntityType.fromClass(entity.getClass()), entity.getId())); } // pick the best pid from the entity. Returns a list (length 1) to save time in the call diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java index ea6e6db82..9b91a545e 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java @@ -5,8 +5,11 @@ import java.io.IOException; import java.util.Optional; import java.util.Properties; -import eu.dnetlib.dhp.oa.dedup.model.OrgSimRel; import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; import org.apache.spark.SparkConf; import org.apache.spark.api.java.function.FilterFunction; import org.apache.spark.api.java.function.MapFunction; @@ -18,6 +21,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.oa.dedup.model.OrgSimRel; import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.schema.oaf.Organization; import eu.dnetlib.dhp.schema.oaf.Relation; @@ -62,6 +66,10 @@ public class SparkPrepareNewOrgs extends AbstractSparkAction { .map(Integer::valueOf) .orElse(NUM_CONNECTIONS); + final String apiUrl = Optional + .ofNullable(parser.get("apiUrl")) + .orElse(""); + final String dbUrl = parser.get("dbUrl"); final String dbTable = parser.get("dbTable"); final String dbUser = parser.get("dbUser"); @@ -72,6 +80,7 @@ public class SparkPrepareNewOrgs extends AbstractSparkAction { log.info("actionSetId: '{}'", actionSetId); log.info("workingPath: '{}'", workingPath); log.info("numPartitions: '{}'", numConnections); + log.info("apiUrl: '{}'", apiUrl); log.info("dbUrl: '{}'", dbUrl); log.info("dbUser: '{}'", dbUser); log.info("table: '{}'", dbTable); @@ -89,9 +98,12 @@ public class SparkPrepareNewOrgs extends AbstractSparkAction { newOrgs .repartition(numConnections) .write() - .mode(SaveMode.Overwrite) + .mode(SaveMode.Append) .jdbc(dbUrl, dbTable, connectionProperties); + if (!apiUrl.isEmpty()) + updateSimRels(apiUrl); + } public static Dataset createNewOrgs( @@ -138,9 +150,21 @@ public class SparkPrepareNewOrgs extends AbstractSparkAction { r._1()._2().getLegalshortname() != null ? r._1()._2().getLegalshortname().getValue() : "", r._1()._2().getCountry() != null ? r._1()._2().getCountry().getClassid() : "", r._1()._2().getWebsiteurl() != null ? r._1()._2().getWebsiteurl().getValue() : "", - r._1()._2().getCollectedfrom().get(0).getValue()), + r._1()._2().getCollectedfrom().get(0).getValue(), ""), Encoders.bean(OrgSimRel.class)); } + private static String updateSimRels(final String apiUrl) throws IOException { + + log.info("Updating simrels on the portal"); + + final HttpGet req = new HttpGet(apiUrl); + try (final CloseableHttpClient client = HttpClients.createDefault()) { + try (final CloseableHttpResponse response = client.execute(req)) { + return IOUtils.toString(response.getEntity().getContent()); + } + } + } + } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java index 4a3ce80df..e9933c4e5 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java @@ -1,18 +1,14 @@ - package eu.dnetlib.dhp.oa.dedup; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.Properties; - +import com.google.common.collect.Lists; +import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.oa.dedup.model.OrgSimRel; +import eu.dnetlib.dhp.schema.common.ModelSupport; +import eu.dnetlib.dhp.schema.oaf.Organization; +import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; import org.apache.commons.io.IOUtils; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; import org.apache.spark.SparkConf; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.sql.Dataset; @@ -21,14 +17,11 @@ import org.apache.spark.sql.SaveMode; import org.apache.spark.sql.SparkSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.schema.common.ModelSupport; -import eu.dnetlib.dhp.schema.oaf.Organization; -import eu.dnetlib.dhp.schema.oaf.Relation; -import eu.dnetlib.dhp.utils.ISLookupClientFactory; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; import scala.Tuple2; +import scala.Tuple3; + +import java.io.IOException; +import java.util.*; public class SparkPrepareOrgRels extends AbstractSparkAction { @@ -67,10 +60,6 @@ public class SparkPrepareOrgRels extends AbstractSparkAction { .map(Integer::valueOf) .orElse(NUM_CONNECTIONS); - final String apiUrl = Optional - .ofNullable(parser.get("apiUrl")) - .orElse(""); - final String dbUrl = parser.get("dbUrl"); final String dbTable = parser.get("dbTable"); final String dbUser = parser.get("dbUser"); @@ -81,7 +70,6 @@ public class SparkPrepareOrgRels extends AbstractSparkAction { log.info("actionSetId: '{}'", actionSetId); log.info("workingPath: '{}'", workingPath); log.info("numPartitions: '{}'", numConnections); - log.info("apiUrl: '{}'", apiUrl); log.info("dbUrl: '{}'", dbUrl); log.info("dbUser: '{}'", dbUser); log.info("table: '{}'", dbTable); @@ -102,9 +90,6 @@ public class SparkPrepareOrgRels extends AbstractSparkAction { .mode(SaveMode.Overwrite) .jdbc(dbUrl, dbTable, connectionProperties); - if (!apiUrl.isEmpty()) - updateSimRels(apiUrl); - } public static Dataset createRelations( @@ -112,6 +97,105 @@ public class SparkPrepareOrgRels extends AbstractSparkAction { final String mergeRelsPath, final String entitiesPath) { + Dataset> entities = spark + .read() + .textFile(entitiesPath) + .map( + (MapFunction>) it -> { + Organization entity = OBJECT_MAPPER.readValue(it, Organization.class); + return new Tuple2<>(entity.getId(), entity); + }, + Encoders.tuple(Encoders.STRING(), Encoders.kryo(Organization.class))); + + Dataset> relations = spark + .createDataset( + spark + .read() + .load(mergeRelsPath) + .as(Encoders.bean(Relation.class)) + .where("relClass == 'merges'") + .toJavaRDD() + .mapToPair(r -> new Tuple2<>(r.getSource(), r.getTarget())) + .filter(t -> !t._2().contains("openorgsmesh")) + .groupByKey() + .map(g -> Lists.newArrayList(g._2())) + .filter(l -> l.size() > 1) + .flatMap(l -> { + String groupId = "group::" + UUID.randomUUID(); + List ids = sortIds(l); + List> rels = new ArrayList<>(); + + for (String source : ids) { + if (source.contains("openorgs____") || ids.indexOf(source) == 0) + for (String target : ids) { + rels.add(new Tuple3<>(source, target, groupId)); + } + } + return rels.iterator(); + }) + .rdd(), + Encoders.tuple(Encoders.STRING(), Encoders.STRING(), Encoders.STRING())); + + Dataset> relations2 = relations // + .joinWith(entities, relations.col("_2").equalTo(entities.col("_1")), "inner") + .map( + (MapFunction, Tuple2>, OrgSimRel>) r -> new OrgSimRel( + r._1()._1(), + r._2()._2().getOriginalId().get(0), + r._2()._2().getLegalname() != null ? r._2()._2().getLegalname().getValue() : "", + r._2()._2().getLegalshortname() != null ? r._2()._2().getLegalshortname().getValue() : "", + r._2()._2().getCountry() != null ? r._2()._2().getCountry().getClassid() : "", + r._2()._2().getWebsiteurl() != null ? r._2()._2().getWebsiteurl().getValue() : "", + r._2()._2().getCollectedfrom().get(0).getValue(), + r._1()._3()), + Encoders.bean(OrgSimRel.class)) + .map( + (MapFunction>) o -> new Tuple2<>(o.getLocal_id(), o), + Encoders.tuple(Encoders.STRING(), Encoders.bean(OrgSimRel.class))); + + return relations2 + .joinWith(entities, relations2.col("_1").equalTo(entities.col("_1")), "inner") + .map( + (MapFunction, Tuple2>, OrgSimRel>) r -> { + OrgSimRel orgSimRel = r._1()._2(); + orgSimRel.setLocal_id(r._2()._2().getOriginalId().get(0)); + return orgSimRel; + }, + Encoders.bean(OrgSimRel.class)); + + } + + // select best ids from the list. Priority: 1) openorgs, 2)corda, 3)alphabetic + public static List sortIds(List ids) { + + ids.sort((o1, o2) -> { + + if (o1.contains("openorgs____") && o2.contains("openorgs____")) + return o1.compareTo(o2); + if (o1.contains("corda") && o2.contains("corda")) + return o1.compareTo(o2); + + if (o1.contains("openorgs____")) + return -1; + if (o2.contains("openorgs____")) + return 1; + + if (o1.contains("corda")) + return -1; + if (o2.contains("corda")) + return 1; + + return o1.compareTo(o2); + }); + + return ids; + } + + public static Dataset createRelationsFromScratch( + final SparkSession spark, + final String mergeRelsPath, + final String entitiesPath) { + // Dataset> entities = spark .read() @@ -151,13 +235,14 @@ public class SparkPrepareOrgRels extends AbstractSparkAction { .joinWith(entities, relations.col("_2").equalTo(entities.col("_1")), "inner") .map( (MapFunction, Tuple2>, OrgSimRel>) r -> new OrgSimRel( - r._1()._1(), - r._2()._2().getOriginalId().get(0), - r._2()._2().getLegalname() != null ? r._2()._2().getLegalname().getValue() : "", - r._2()._2().getLegalshortname() != null ? r._2()._2().getLegalshortname().getValue() : "", - r._2()._2().getCountry() != null ? r._2()._2().getCountry().getClassid() : "", - r._2()._2().getWebsiteurl() != null ? r._2()._2().getWebsiteurl().getValue() : "", - r._2()._2().getCollectedfrom().get(0).getValue()), + r._1()._1(), + r._2()._2().getOriginalId().get(0), + r._2()._2().getLegalname() != null ? r._2()._2().getLegalname().getValue() : "", + r._2()._2().getLegalshortname() != null ? r._2()._2().getLegalshortname().getValue() : "", + r._2()._2().getCountry() != null ? r._2()._2().getCountry().getClassid() : "", + r._2()._2().getWebsiteurl() != null ? r._2()._2().getWebsiteurl().getValue() : "", + r._2()._2().getCollectedfrom().get(0).getValue(), + "group::" + r._1()._1()), Encoders.bean(OrgSimRel.class)) .map( (MapFunction>) o -> new Tuple2<>(o.getLocal_id(), o), @@ -175,16 +260,4 @@ public class SparkPrepareOrgRels extends AbstractSparkAction { } - private static String updateSimRels(final String apiUrl) throws IOException { - - log.info("Updating simrels on the portal"); - - final HttpGet req = new HttpGet(apiUrl); - try (final CloseableHttpClient client = HttpClients.createDefault()) { - try (final CloseableHttpResponse response = client.execute(req)) { - return IOUtils.toString(response.getEntity().getContent()); - } - } - } - } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/graph/ConnectedComponent.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/graph/ConnectedComponent.java index 3d0d24d23..3f24adb93 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/graph/ConnectedComponent.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/graph/ConnectedComponent.java @@ -6,13 +6,13 @@ import java.io.Serializable; import java.util.Set; import java.util.stream.Collectors; -import eu.dnetlib.dhp.utils.DHPUtils; import org.apache.commons.lang.StringUtils; import org.codehaus.jackson.annotate.JsonIgnore; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.oa.dedup.DedupUtility; +import eu.dnetlib.dhp.utils.DHPUtils; import eu.dnetlib.pace.util.PaceException; public class ConnectedComponent implements Serializable { diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java index 95fd21c64..f5b2f48c5 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import com.google.common.collect.Sets; import eu.dnetlib.dhp.oa.dedup.IdGenerator; import eu.dnetlib.dhp.schema.common.EntityType; import eu.dnetlib.dhp.schema.oaf.KeyValue; @@ -95,8 +96,13 @@ public class Identifier implements Serializable, Comparable { // priority in comparisons: 1) pidtype, 2) collectedfrom (depending on the entity type) , 3) date 4) // alphabetical order of the originalID - Set lKeys = this.collectedFrom.stream().map(KeyValue::getKey).collect(Collectors.toSet()); - Set rKeys = i.getCollectedFrom().stream().map(KeyValue::getKey).collect(Collectors.toSet()); + Set lKeys = Sets.newHashSet(); + if (this.collectedFrom != null) + lKeys = this.collectedFrom.stream().map(KeyValue::getKey).collect(Collectors.toSet()); + + Set rKeys = Sets.newHashSet(); + if (i.getCollectedFrom() != null) + rKeys = i.getCollectedFrom().stream().map(KeyValue::getKey).collect(Collectors.toSet()); if (this.getType().compareTo(i.getType()) == 0) { // same type if (entityType == EntityType.publication) { diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/OrgSimRel.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/OrgSimRel.java index 50164ce4c..65f383500 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/OrgSimRel.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/OrgSimRel.java @@ -12,12 +12,13 @@ public class OrgSimRel implements Serializable { String oa_country; String oa_url; String oa_collectedfrom; + String group_id; public OrgSimRel() { } public OrgSimRel(String local_id, String oa_original_id, String oa_name, String oa_acronym, String oa_country, - String oa_url, String oa_collectedfrom) { + String oa_url, String oa_collectedfrom, String group_id) { this.local_id = local_id; this.oa_original_id = oa_original_id; this.oa_name = oa_name; @@ -25,6 +26,7 @@ public class OrgSimRel implements Serializable { this.oa_country = oa_country; this.oa_url = oa_url; this.oa_collectedfrom = oa_collectedfrom; + this.group_id = group_id; } public String getLocal_id() { @@ -83,6 +85,14 @@ public class OrgSimRel implements Serializable { this.oa_collectedfrom = oa_collectedfrom; } + public String getGroup_id() { + return group_id; + } + + public void setGroup_id(String group_id) { + this.group_id = group_id; + } + @Override public String toString() { return "OrgSimRel{" + diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/workflow.xml index ec9967d6a..e7c95ee8d 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/workflow.xml @@ -112,7 +112,7 @@ -pb - ${graphBasePath}/relation + /tmp/graph_openorgs_and_corda/relation ${workingPath}/${actionSetId}/organization_simrel @@ -194,6 +194,37 @@ --workingPath${workingPath} --isLookUpUrl${isLookUpUrl} --actionSetId${actionSetId} + --dbUrl${dbUrl} + --dbTable${dbTable} + --dbUser${dbUser} + --dbPwd${dbPwd} + --numConnections20 + + + + + + + + yarn + cluster + Prepare New Organizations + eu.dnetlib.dhp.oa.dedup.SparkPrepareNewOrgs + dhp-dedup-openaire-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=3840 + + --graphBasePath${graphBasePath} + --workingPath${workingPath} + --isLookUpUrl${isLookUpUrl} + --actionSetId${actionSetId} --apiUrl${apiUrl} --dbUrl${dbUrl} --dbTable${dbTable} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareNewOrgs_parameters.json b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareNewOrgs_parameters.json index 2119cbc3a..b70d1af28 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareNewOrgs_parameters.json +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareNewOrgs_parameters.json @@ -29,6 +29,12 @@ "paramDescription": "number of connections to the postgres db (for the write operation)", "paramRequired": false }, + { + "paramName": "au", + "paramLongName": "apiUrl", + "paramDescription": "the url for the APIs of the openorgs service", + "paramRequired": false + }, { "paramName": "du", "paramLongName": "dbUrl", diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json index b70d1af28..2119cbc3a 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json @@ -29,12 +29,6 @@ "paramDescription": "number of connections to the postgres db (for the write operation)", "paramRequired": false }, - { - "paramName": "au", - "paramLongName": "apiUrl", - "paramDescription": "the url for the APIs of the openorgs service", - "paramRequired": false - }, { "paramName": "du", "paramLongName": "dbUrl", diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java index 1bf851527..403498aeb 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java @@ -1,17 +1,6 @@ + package eu.dnetlib.dhp.oa.dedup; -import com.google.common.collect.Lists; -import eu.dnetlib.dhp.oa.dedup.model.Identifier; -import eu.dnetlib.dhp.oa.dedup.model.PidType; -import eu.dnetlib.dhp.schema.common.EntityType; -import eu.dnetlib.dhp.schema.oaf.KeyValue; -import eu.dnetlib.dhp.schema.oaf.Publication; -import eu.dnetlib.dhp.schema.oaf.Qualifier; -import eu.dnetlib.dhp.schema.oaf.StructuredProperty; -import eu.dnetlib.pace.util.MapDocumentUtil; -import org.codehaus.jackson.map.ObjectMapper; -import org.junit.jupiter.api.*; -import scala.Tuple2; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.BufferedReader; @@ -22,119 +11,149 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.stream.Collectors; + +import org.codehaus.jackson.map.ObjectMapper; +import org.junit.jupiter.api.*; + +import com.google.common.collect.Lists; + +import eu.dnetlib.dhp.oa.dedup.model.Identifier; +import eu.dnetlib.dhp.oa.dedup.model.PidType; +import eu.dnetlib.dhp.schema.common.EntityType; +import eu.dnetlib.dhp.schema.oaf.KeyValue; +import eu.dnetlib.dhp.schema.oaf.Publication; +import eu.dnetlib.dhp.schema.oaf.Qualifier; +import eu.dnetlib.dhp.schema.oaf.StructuredProperty; +import eu.dnetlib.pace.util.MapDocumentUtil; +import scala.Tuple2; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class IdGeneratorTest { - private static List bestIds; - private static List> pubs; + private static List bestIds; + private static List> pubs; - private static List bestIds2; - private static List bestIds3; + private static List bestIds2; + private static List bestIds3; - private static String testEntityBasePath; + private static String testEntityBasePath; - private static SimpleDateFormat sdf; - private static Date baseDate; + private static SimpleDateFormat sdf; + private static Date baseDate; - @BeforeAll - public static void setUp() throws Exception { + @BeforeAll + public static void setUp() throws Exception { - sdf = new SimpleDateFormat("yyyy-MM-dd"); - baseDate = sdf.parse("2000-01-01"); + sdf = new SimpleDateFormat("yyyy-MM-dd"); + baseDate = sdf.parse("2000-01-01"); - bestIds = new ArrayList<>(); - bestIds2 = Lists.newArrayList( - new Identifier(pid("pid1", "original", "original"), baseDate, PidType.original, keyValue("key", "value"), EntityType.publication, "50|originalID1"), - new Identifier(pid("pid2", "original", "original"), baseDate, PidType.original, keyValue("key", "value"), EntityType.publication, "50|originalID2"), - new Identifier(pid("pid3", "original", "original"), baseDate, PidType.original, keyValue("key", "value"), EntityType.publication, "50|originalID3") - ); - bestIds3 = Lists.newArrayList( - new Identifier(pid("pid1", "original", "original"), baseDate, PidType.original, keyValue("key", "value"), EntityType.publication, "50|originalID1"), - new Identifier(pid("pid2", "doi", "doi"), baseDate, PidType.doi, keyValue("key", "value"), EntityType.publication, "50|originalID2"), - new Identifier(pid("pid3", "original", "original"), baseDate, PidType.original, keyValue("key", "value"), EntityType.publication, "50|originalID3") - ); + bestIds = new ArrayList<>(); + bestIds2 = Lists + .newArrayList( + new Identifier(pid("pid1", "original", "original"), baseDate, PidType.original, + keyValue("key", "value"), EntityType.publication, "50|originalID1"), + new Identifier(pid("pid2", "original", "original"), baseDate, PidType.original, + keyValue("key", "value"), EntityType.publication, "50|originalID2"), + new Identifier(pid("pid3", "original", "original"), baseDate, PidType.original, + keyValue("key", "value"), EntityType.publication, "50|originalID3")); + bestIds3 = Lists + .newArrayList( + new Identifier(pid("pid1", "original", "original"), baseDate, PidType.original, + keyValue("key", "value"), EntityType.publication, "50|originalID1"), + new Identifier(pid("pid2", "doi", "doi"), baseDate, PidType.doi, keyValue("key", "value"), + EntityType.publication, "50|originalID2"), + new Identifier(pid("pid3", "original", "original"), baseDate, PidType.original, + keyValue("key", "value"), EntityType.publication, "50|originalID3")); - testEntityBasePath = Paths - .get(SparkDedupTest.class.getResource("/eu/dnetlib/dhp/dedup/json").toURI()) - .toFile() - .getAbsolutePath(); + testEntityBasePath = Paths + .get(SparkDedupTest.class.getResource("/eu/dnetlib/dhp/dedup/json").toURI()) + .toFile() + .getAbsolutePath(); - pubs = readSample(testEntityBasePath + "/publication_idgeneration.json", Publication.class); + pubs = readSample(testEntityBasePath + "/publication_idgeneration.json", Publication.class); - } + } - @Test - @Order(1) - public void bestPidToIdentifierTest(){ + @Test + @Order(1) + public void bestPidToIdentifierTest() { - List typesForAssertions = Lists.newArrayList(PidType.pmc.toString(), PidType.doi.toString(), PidType.doi.toString()); + List typesForAssertions = Lists + .newArrayList(PidType.pmc.toString(), PidType.doi.toString(), PidType.doi.toString()); - for (Tuple2 pub : pubs) { - List ids = IdGenerator.bestPidToIdentifier(pub._2()); - assertEquals(typesForAssertions.get(pubs.indexOf(pub)), ids.get(0).getPid().getQualifier().getClassid()); - bestIds.addAll(ids); - } - } + for (Tuple2 pub : pubs) { + List ids = IdGenerator.bestPidToIdentifier(pub._2()); + assertEquals(typesForAssertions.get(pubs.indexOf(pub)), ids.get(0).getPid().getQualifier().getClassid()); + bestIds.addAll(ids); + } + } - @Test - @Order(2) - public void generateIdTest1(){ - String id1 = IdGenerator.generate(bestIds, "50|defaultID"); + @Test + @Order(2) + public void generateIdTest1() { + String id1 = IdGenerator.generate(bestIds, "50|defaultID"); - assertEquals("50|dedup_doi___::84f2cc49e3af11f20952eae15cdae066", id1); - } + System.out.println("id list 1 = " + bestIds.stream().map(i -> i.getPid().getValue()).collect(Collectors.toList())); - @Test - public void generateIdTest2(){ - String id1 = IdGenerator.generate(bestIds2, "50|defaultID"); - String id2 = IdGenerator.generate(bestIds3, "50|defaultID"); + assertEquals("50|dedup_wf_001::9c5cfbf993d38476e0f959a301239719", id1); + } - assertEquals("50|dedup_wf_001::2c56cc1914bffdb30fdff354e0099612", id1); - assertEquals("50|dedup_doi___::128ead3ed8d9ecf262704b6fcf592b8d", id2); - } + @Test + public void generateIdTest2() { + String id1 = IdGenerator.generate(bestIds2, "50|defaultID"); + String id2 = IdGenerator.generate(bestIds3, "50|defaultID"); - public static List> readSample(String path, Class clazz) { - List> res = new ArrayList<>(); - BufferedReader reader; - try { - reader = new BufferedReader(new FileReader(path)); - String line = reader.readLine(); - while (line != null) { - res - .add( - new Tuple2<>( - MapDocumentUtil.getJPathString("$.id", line), - new ObjectMapper().readValue(line, clazz))); - // read next line - line = reader.readLine(); - } - reader.close(); - } catch (IOException e) { - e.printStackTrace(); - } + System.out.println("id list 2 = " + bestIds2.stream().map(i -> i.getPid().getValue()).collect(Collectors.toList())); + System.out.println("winner 2 = " + id1); + System.out.println("id list 3 = " + bestIds3.stream().map(i -> i.getPid().getValue()).collect(Collectors.toList())); + System.out.println("winner 3 = " + id2); - return res; - } + assertEquals("50|dedup_wf_001::2c56cc1914bffdb30fdff354e0099612", id1); + assertEquals("50|dedup_doi___::128ead3ed8d9ecf262704b6fcf592b8d", id2); + } - public static StructuredProperty pid(String pid, String classid, String classname){ + public static List> readSample(String path, Class clazz) { + List> res = new ArrayList<>(); + BufferedReader reader; + try { + reader = new BufferedReader(new FileReader(path)); + String line = reader.readLine(); + while (line != null) { + res + .add( + new Tuple2<>( + MapDocumentUtil.getJPathString("$.id", line), + new ObjectMapper().readValue(line, clazz))); + // read next line + line = reader.readLine(); + } + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } - StructuredProperty sp = new StructuredProperty(); - sp.setValue(pid); - Qualifier q = new Qualifier(); - q.setSchemeid(classid); - q.setSchemename(classname); - q.setClassname(classname); - q.setClassid(classid); - sp.setQualifier(q); - return sp; - } + return res; + } - public static List keyValue(String key, String value){ + public static StructuredProperty pid(String pid, String classid, String classname) { - KeyValue kv = new KeyValue(); - kv.setKey(key); - kv.setValue(value); - return Lists.newArrayList(kv); - } + StructuredProperty sp = new StructuredProperty(); + sp.setValue(pid); + Qualifier q = new Qualifier(); + q.setSchemeid(classid); + q.setSchemename(classname); + q.setClassname(classname); + q.setClassid(classid); + sp.setQualifier(q); + return sp; + } + + public static List keyValue(String key, String value) { + + KeyValue kv = new KeyValue(); + kv.setKey(key); + kv.setValue(value); + return Lists.newArrayList(kv); + } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java index 6516eda52..b849160ff 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java @@ -1,18 +1,12 @@ package eu.dnetlib.dhp.oa.dedup; -import static java.nio.file.Files.createTempDirectory; - -import static org.apache.spark.sql.functions.count; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.lenient; - -import java.io.File; -import java.io.IOException; -import java.io.Serializable; -import java.net.URISyntaxException; -import java.nio.file.Paths; - +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import eu.dnetlib.pace.util.MapDocumentUtil; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; @@ -22,22 +16,28 @@ import org.apache.spark.api.java.JavaSparkContext; import org.apache.spark.api.java.function.FilterFunction; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.api.java.function.PairFunction; -import org.apache.spark.sql.*; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.Row; +import org.apache.spark.sql.SparkSession; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.schema.oaf.Relation; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import eu.dnetlib.pace.util.MapDocumentUtil; import scala.Tuple2; +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.net.URISyntaxException; +import java.nio.file.Paths; + +import static java.nio.file.Files.createTempDirectory; +import static org.apache.spark.sql.functions.count; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.lenient; + @ExtendWith(MockitoExtension.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class SparkDedupTest implements Serializable { From 708d887e64bcc2038d94d769fd2a025a850b3ebe Mon Sep 17 00:00:00 2001 From: miconis Date: Tue, 20 Oct 2020 15:12:19 +0200 Subject: [PATCH 016/445] minor changes --- .../test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java index 30b213ff2..c2bbf9e0e 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java @@ -53,6 +53,7 @@ public class EntityMergerTest implements Serializable { @Test public void softwareMergerTest() throws InstantiationException, IllegalAccessException { + List> softwares = readSample( testEntityBasePath + "/software_merge.json", Software.class); @@ -131,7 +132,6 @@ public class EntityMergerTest implements Serializable { assertEquals("50|dedup_doi___::0ca46ff10b2b4c756191719d85302b14", pub_merged.getId()); assertEquals(pub_merged.getAuthor().size(), 27); - } @Test @@ -142,7 +142,6 @@ public class EntityMergerTest implements Serializable { // verify id assertEquals("50|dedup_doi___::0ca46ff10b2b4c756191719d85302b14", pub_merged.getId()); - } @Test @@ -153,7 +152,6 @@ public class EntityMergerTest implements Serializable { // verify id assertEquals("50|dedup_wf_001::2d2bbbbcfb285e3fb3590237b79e2fa8", pub_merged.getId()); - } @Test @@ -164,7 +162,6 @@ public class EntityMergerTest implements Serializable { // verify id assertEquals("50|dedup_wf_001::584b89679c3ccd1015b647ec63cc2699", pub_merged.getId()); - } public DataInfo setDI() { From 58f28296ea2a8eb2ed1e2c9c68f9a35107a19da1 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Fri, 30 Oct 2020 10:56:42 +0100 Subject: [PATCH 017/445] ProvisionConstants moved as ModelHardLimits in dhp-common and applied to truncate long abstracts (len > 150000). Further filtering for empty PID values --- .../dhp/schema/oaf/ModelHardLimits.java | 6 +-- .../schema/oaf/utils/IdentifierFactory.java | 38 ++++++++++++++++--- .../raw/AbstractMdRecordToOafMapper.java | 1 + .../raw/GenerateEntitiesApplication.java | 14 ++----- .../dhp/oa/graph/raw/OafToOafMapper.java | 16 +++++++- .../dhp/oa/graph/raw/OdfToOafMapper.java | 16 +++++++- .../CreateRelatedEntitiesJob_phase1.java | 4 +- .../CreateRelatedEntitiesJob_phase2.java | 12 +++--- 8 files changed, 75 insertions(+), 32 deletions(-) rename dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/ProvisionConstants.java => dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ModelHardLimits.java (70%) diff --git a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/ProvisionConstants.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ModelHardLimits.java similarity index 70% rename from dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/ProvisionConstants.java rename to dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ModelHardLimits.java index 9bc3706cd..16fdc3760 100644 --- a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/ProvisionConstants.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ModelHardLimits.java @@ -1,14 +1,14 @@ -package eu.dnetlib.dhp.oa.provision; +package eu.dnetlib.dhp.schema.oaf; -public class ProvisionConstants { +public class ModelHardLimits { public static final int MAX_EXTERNAL_ENTITIES = 50; public static final int MAX_AUTHORS = 200; public static final int MAX_AUTHOR_FULLNAME_LENGTH = 1000; public static final int MAX_TITLE_LENGTH = 5000; public static final int MAX_TITLES = 10; - public static final int MAX_ABSTRACT_LENGTH = 100000; + public static final int MAX_ABSTRACT_LENGTH = 150000; public static final int MAX_INSTANCES = 10; } diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 183f68904..3059459d6 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -3,6 +3,7 @@ package eu.dnetlib.dhp.schema.oaf.utils; import java.io.Serializable; import java.util.Objects; +import java.util.Optional; import org.apache.commons.lang.StringUtils; @@ -15,12 +16,21 @@ import eu.dnetlib.dhp.utils.DHPUtils; */ public class IdentifierFactory implements Serializable { + public static final String DOI_URL_PREFIX = "^http(s?):\\/\\/(dx\\.)?doi\\.org\\/"; + public static final String ID_SEPARATOR = "::"; public static final String ID_PREFIX_SEPARATOR = "|"; public final static String ID_REGEX = "^[0-9][0-9]\\" + ID_PREFIX_SEPARATOR + ".{12}" + ID_SEPARATOR + "[a-zA-Z0-9]{32}$"; public static final int ID_PREFIX_LEN = 12; + /** + * Creates an identifier from the most relevant PID (if available) in the given entity T. Returns entity.id + * when no PID is available + * @param entity the entity providing PIDs and a default ID. + * @param the specific entity type. Currently Organization and Result subclasses are supported. + * @return an identifier from the most relevant PID, entity.id otherwise + */ public static String createIdentifier(T entity) { if (Objects.isNull(entity.getPid()) || entity.getPid().isEmpty()) { @@ -32,12 +42,33 @@ public class IdentifierFactory implements Serializable { .stream() .filter(s -> Objects.nonNull(s.getQualifier())) .filter(s -> PidType.isValid(s.getQualifier().getClassid())) + .filter(s -> StringUtils.isNotBlank(StringUtils.trim(s.getValue()))) .min(new PidComparator<>(entity)) .map(s -> idFromPid(entity, s)) .map(IdentifierFactory::verifyIdSyntax) .orElseGet(entity::getId); } + /** + * Utility method that normalises PID values on a per-type basis. + * @param pid the PID whose value will be normalised. + * @return the PID containing the normalised value. + */ + public static StructuredProperty normalizePidValue(StructuredProperty pid) { + String value = Optional + .ofNullable(pid.getValue()) + .map(String::trim) + .orElseThrow(() -> new IllegalArgumentException("PID value cannot be empty")); + switch (pid.getQualifier().getClassid()) { + + // TODO add cleaning for more PID types as needed + case "doi": + pid.setValue(value.toLowerCase().replaceAll(DOI_URL_PREFIX, "")); + break; + } + return pid; + } + private static String verifyIdSyntax(String s) { if (StringUtils.isBlank(s) || !s.matches(ID_REGEX)) { throw new RuntimeException(String.format("malformed id: '%s'", s)); @@ -52,15 +83,10 @@ public class IdentifierFactory implements Serializable { .append(ID_PREFIX_SEPARATOR) .append(createPrefix(s.getQualifier().getClassid())) .append(ID_SEPARATOR) - .append(DHPUtils.md5(normalizePidValue(s.getValue()))) + .append(DHPUtils.md5(normalizePidValue(s).getValue())) .toString(); } - private static String normalizePidValue(String value) { - // TODO more aggressive cleaning? keep only alphanum and punctuation? - return value.toLowerCase().replaceAll(" ", ""); - } - // create the prefix (length = 12) private static String createPrefix(String pidType) { StringBuilder prefix = new StringBuilder(StringUtils.left(pidType, ID_PREFIX_LEN)); diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java index 37c479e5f..57e0aa49e 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java @@ -561,4 +561,5 @@ public abstract class AbstractMdRecordToOafMapper { } return res; } + } diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java index 7b13e500e..2dbe4eb83 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java @@ -116,17 +116,9 @@ public class GenerateEntitiesApplication { private static Oaf merge(final Oaf o1, final Oaf o2) { if (ModelSupport.isSubClass(o1, OafEntity.class)) { if (ModelSupport.isSubClass(o1, Result.class)) { - if (ModelSupport.isSubClass(o1, Publication.class)) { - ((Publication) o1).mergeFrom((Publication) o2); - } else if (ModelSupport.isSubClass(o1, Dataset.class)) { - ((Dataset) o1).mergeFrom((Dataset) o2); - } else if (ModelSupport.isSubClass(o1, Software.class)) { - ((Software) o1).mergeFrom((Software) o2); - } else if (ModelSupport.isSubClass(o1, OtherResearchProduct.class)) { - ((OtherResearchProduct) o1).mergeFrom((OtherResearchProduct) o2); - } else { - throw new RuntimeException("invalid Result subtype:" + o1.getClass().getCanonicalName()); - } + + // We cannot further specify the result type as different result types might share the same ID + ((Result) o1).mergeFrom((Result) o2); } else if (ModelSupport.isSubClass(o1, Datasource.class)) { ((Datasource) o1).mergeFrom((Datasource) o2); } else if (ModelSupport.isSubClass(o1, Organization.class)) { diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java index 543d22eeb..4813a202b 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java @@ -8,6 +8,7 @@ import static eu.dnetlib.dhp.schema.common.ModelConstants.*; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; @@ -20,6 +21,7 @@ import com.google.common.collect.Lists; import eu.dnetlib.dhp.common.PacePerson; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; import eu.dnetlib.dhp.schema.oaf.*; +import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory; public class OafToOafMapper extends AbstractMdRecordToOafMapper { @@ -85,7 +87,13 @@ public class OafToOafMapper extends AbstractMdRecordToOafMapper { @Override protected List> prepareDescriptions(final Document doc, final DataInfo info) { - return prepareListFields(doc, "//dc:description", info); + return prepareListFields(doc, "//dc:description", info) + .stream() + .map(d -> { + d.setValue(StringUtils.left(d.getValue(), ModelHardLimits.MAX_ABSTRACT_LENGTH)); + return d; + }) + .collect(Collectors.toList()); } @Override @@ -283,6 +291,10 @@ public class OafToOafMapper extends AbstractMdRecordToOafMapper { @Override protected List prepareResultPids(final Document doc, final DataInfo info) { return prepareListStructPropsWithValidQualifier( - doc, "//oaf:identifier", "@identifierType", DNET_PID_TYPES, info); + doc, "//oaf:identifier", "@identifierType", DNET_PID_TYPES, info) + .stream() + .map(IdentifierFactory::normalizePidValue) + .collect(Collectors.toList()); } + } diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java index 0319cb89e..d819de1cb 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java @@ -11,6 +11,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.dom4j.Document; @@ -22,6 +23,7 @@ import eu.dnetlib.dhp.common.PacePerson; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.*; +import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory; public class OdfToOafMapper extends AbstractMdRecordToOafMapper { @@ -191,7 +193,13 @@ public class OdfToOafMapper extends AbstractMdRecordToOafMapper { @Override protected List> prepareDescriptions(final Document doc, final DataInfo info) { - return prepareListFields(doc, "//datacite:description[@descriptionType='Abstract']", info); + return prepareListFields(doc, "//datacite:description[@descriptionType='Abstract']", info) + .stream() + .map(d -> { + d.setValue(StringUtils.left(d.getValue(), ModelHardLimits.MAX_ABSTRACT_LENGTH)); + return d; + }) + .collect(Collectors.toList()); } @Override @@ -371,7 +379,11 @@ public class OdfToOafMapper extends AbstractMdRecordToOafMapper { doc, "//datacite:alternateIdentifier[@alternateIdentifierType != 'URL' and @alternateIdentifierType != 'landingPage']", "@alternateIdentifierType", DNET_PID_TYPES, info)); - return Lists.newArrayList(res); + + return res + .stream() + .map(IdentifierFactory::normalizePidValue) + .collect(Collectors.toList()); } } diff --git a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/CreateRelatedEntitiesJob_phase1.java b/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/CreateRelatedEntitiesJob_phase1.java index b08e593f7..d404850eb 100644 --- a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/CreateRelatedEntitiesJob_phase1.java +++ b/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/CreateRelatedEntitiesJob_phase1.java @@ -164,7 +164,7 @@ public class CreateRelatedEntitiesJob_phase1 { if (result.getTitle() != null && !result.getTitle().isEmpty()) { final StructuredProperty title = result.getTitle().stream().findFirst().get(); - title.setValue(StringUtils.left(title.getValue(), ProvisionConstants.MAX_TITLE_LENGTH)); + title.setValue(StringUtils.left(title.getValue(), ModelHardLimits.MAX_TITLE_LENGTH)); re.setTitle(title); } @@ -178,7 +178,7 @@ public class CreateRelatedEntitiesJob_phase1 { .getInstance() .stream() .filter(Objects::nonNull) - .limit(ProvisionConstants.MAX_INSTANCES) + .limit(ModelHardLimits.MAX_INSTANCES) .collect(Collectors.toList())); } diff --git a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/CreateRelatedEntitiesJob_phase2.java b/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/CreateRelatedEntitiesJob_phase2.java index 7e175121e..e32fe020b 100644 --- a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/CreateRelatedEntitiesJob_phase2.java +++ b/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/CreateRelatedEntitiesJob_phase2.java @@ -240,15 +240,15 @@ public class CreateRelatedEntitiesJob_phase2 { List refs = r .getExternalReference() .stream() - .limit(ProvisionConstants.MAX_EXTERNAL_ENTITIES) + .limit(ModelHardLimits.MAX_EXTERNAL_ENTITIES) .collect(Collectors.toList()); r.setExternalReference(refs); } if (r.getAuthor() != null) { List authors = Lists.newArrayList(); for (Author a : r.getAuthor()) { - a.setFullname(StringUtils.left(a.getFullname(), ProvisionConstants.MAX_AUTHOR_FULLNAME_LENGTH)); - if (authors.size() < ProvisionConstants.MAX_AUTHORS || hasORCID(a)) { + a.setFullname(StringUtils.left(a.getFullname(), ModelHardLimits.MAX_AUTHOR_FULLNAME_LENGTH)); + if (authors.size() < ModelHardLimits.MAX_AUTHORS || hasORCID(a)) { authors.add(a); } } @@ -260,7 +260,7 @@ public class CreateRelatedEntitiesJob_phase2 { .stream() .filter(Objects::nonNull) .map(d -> { - d.setValue(StringUtils.left(d.getValue(), ProvisionConstants.MAX_ABSTRACT_LENGTH)); + d.setValue(StringUtils.left(d.getValue(), ModelHardLimits.MAX_ABSTRACT_LENGTH)); return d; }) .collect(Collectors.toList()); @@ -272,10 +272,10 @@ public class CreateRelatedEntitiesJob_phase2 { .stream() .filter(Objects::nonNull) .map(t -> { - t.setValue(StringUtils.left(t.getValue(), ProvisionConstants.MAX_TITLE_LENGTH)); + t.setValue(StringUtils.left(t.getValue(), ModelHardLimits.MAX_TITLE_LENGTH)); return t; }) - .limit(ProvisionConstants.MAX_TITLES) + .limit(ModelHardLimits.MAX_TITLES) .collect(Collectors.toList()); r.setTitle(titles); } From 04ad8969b20859112ad49bae2bcebd5841b5b457 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Fri, 30 Oct 2020 15:46:55 +0100 Subject: [PATCH 018/445] anticipated execution of the graph cleaning workflow --- .../wf/profiles/graph_prod_construction.xml | 66 +++++++++++++++---- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/graph_prod_construction.xml b/dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/graph_prod_construction.xml index 047433320..4eab12c73 100644 --- a/dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/graph_prod_construction.xml +++ b/dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/graph_prod_construction.xml @@ -44,6 +44,7 @@ + Set the target path to store the RAW graph @@ -54,31 +55,45 @@ + + + Set the target path to store the first CLEANED graph + + firstCleanedGraphPath + /tmp/prod_provision/graph/02_graph_first_cleaned + + + + + + Set the target path to store the DEDUPED graph dedupGraphPath - /tmp/beta_provision/graph/02_graph_dedup + /tmp/beta_provision/graph/03_graph_dedup + Set the target path to store the INFERRED graph inferredGraphPath - /tmp/beta_provision/graph/03_graph_inferred + /tmp/beta_provision/graph/04_graph_inferred + Set the target path to store the CONSISTENCY graph consistentGraphPath - /tmp/beta_provision/graph/04_graph_consistent + /tmp/beta_provision/graph/05_graph_consistent @@ -89,7 +104,7 @@ Set the target path to store the ORCID enriched graph orcidGraphPath - /tmp/beta_provision/graph/05_graph_orcid + /tmp/beta_provision/graph/06_graph_orcid @@ -100,7 +115,7 @@ Set the target path to store the BULK TAGGED graph bulkTaggingGraphPath - /tmp/beta_provision/graph/06_graph_bulktagging + /tmp/beta_provision/graph/07_graph_bulktagging @@ -111,7 +126,7 @@ Set the target path to store the AFFILIATION from INSTITUTIONAL REPOS graph affiliationGraphPath - /tmp/beta_provision/graph/07_graph_affiliation + /tmp/beta_provision/graph/08_graph_affiliation @@ -122,7 +137,7 @@ Set the target path to store the COMMUNITY from SELECTED SOURCES graph communityOrganizationGraphPath - /tmp/beta_provision/graph/08_graph_comunity_organization + /tmp/beta_provision/graph/09_graph_comunity_organization @@ -133,7 +148,7 @@ Set the target path to store the FUNDING from SEMANTIC RELATION graph fundingGraphPath - /tmp/beta_provision/graph/09_graph_funding + /tmp/beta_provision/graph/10_graph_funding @@ -144,7 +159,7 @@ Set the target path to store the COMMUNITY from SEMANTIC RELATION graph communitySemRelGraphPath - /tmp/beta_provision/graph/10_graph_comunity_sem_rel + /tmp/beta_provision/graph/11_graph_comunity_sem_rel @@ -155,7 +170,7 @@ Set the target path to store the COUNTRY enriched graph countryGraphPath - /tmp/beta_provision/graph/11_graph_country + /tmp/beta_provision/graph/12_graph_country @@ -166,7 +181,7 @@ Set the target path to store the CLEANED graph cleanedGraphPath - /tmp/beta_provision/graph/12_graph_cleaned + /tmp/beta_provision/graph/13_graph_cleaned @@ -177,7 +192,7 @@ Set the target path to store the blacklisted graph blacklistedGraphPath - /tmp/beta_provision/graph/13_graph_blacklisted + /tmp/beta_provision/graph/14_graph_blacklisted @@ -324,6 +339,31 @@ build-report + + + + + + + clean the properties in the graph typed as Qualifier according to the vocabulary indicated in schemeid + + executeOozieJob + IIS + + { + 'graphInputPath' : 'rawGraphPath', + 'graphOutputPath': 'firstCleanedGraphPath', + 'isLookupUrl': 'isLookUpUrl' + } + + + { + 'oozie.wf.application.path' : '/lib/dnet/oa/graph/clean/oozie_app', + 'workingPath' : '/tmp/beta_provision/working_dir/first_clean' + } + + build-report + @@ -337,7 +377,7 @@ { 'actionSetId' : 'dedupConfig', - 'graphBasePath' : 'rawGraphPath', + 'graphBasePath' : 'firstCleanedGraphPath', 'dedupGraphPath': 'dedupGraphPath', 'isLookUpUrl' : 'isLookUpUrl' } From 385214eeae3efd6768588b0da909a8fc29a13bab Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Fri, 30 Oct 2020 15:47:05 +0100 Subject: [PATCH 019/445] code formatting --- .../dhp/oa/dedup/SparkPrepareOrgRels.java | 41 ++++++++++--------- .../dhp/oa/dedup/model/Identifier.java | 1 + .../dnetlib/dhp/oa/dedup/IdGeneratorTest.java | 9 ++-- .../dnetlib/dhp/oa/dedup/SparkDedupTest.java | 37 +++++++++-------- 4 files changed, 49 insertions(+), 39 deletions(-) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java index e9933c4e5..0510b1a90 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java @@ -1,13 +1,9 @@ + package eu.dnetlib.dhp.oa.dedup; -import com.google.common.collect.Lists; -import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.oa.dedup.model.OrgSimRel; -import eu.dnetlib.dhp.schema.common.ModelSupport; -import eu.dnetlib.dhp.schema.oaf.Organization; -import eu.dnetlib.dhp.schema.oaf.Relation; -import eu.dnetlib.dhp.utils.ISLookupClientFactory; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import java.io.IOException; +import java.util.*; + import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; import org.apache.spark.api.java.function.MapFunction; @@ -17,12 +13,19 @@ import org.apache.spark.sql.SaveMode; import org.apache.spark.sql.SparkSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + +import com.google.common.collect.Lists; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.oa.dedup.model.OrgSimRel; +import eu.dnetlib.dhp.schema.common.ModelSupport; +import eu.dnetlib.dhp.schema.oaf.Organization; +import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; import scala.Tuple2; import scala.Tuple3; -import java.io.IOException; -import java.util.*; - public class SparkPrepareOrgRels extends AbstractSparkAction { private static final Logger log = LoggerFactory.getLogger(SparkCreateDedupRecord.class); @@ -235,14 +238,14 @@ public class SparkPrepareOrgRels extends AbstractSparkAction { .joinWith(entities, relations.col("_2").equalTo(entities.col("_1")), "inner") .map( (MapFunction, Tuple2>, OrgSimRel>) r -> new OrgSimRel( - r._1()._1(), - r._2()._2().getOriginalId().get(0), - r._2()._2().getLegalname() != null ? r._2()._2().getLegalname().getValue() : "", - r._2()._2().getLegalshortname() != null ? r._2()._2().getLegalshortname().getValue() : "", - r._2()._2().getCountry() != null ? r._2()._2().getCountry().getClassid() : "", - r._2()._2().getWebsiteurl() != null ? r._2()._2().getWebsiteurl().getValue() : "", - r._2()._2().getCollectedfrom().get(0).getValue(), - "group::" + r._1()._1()), + r._1()._1(), + r._2()._2().getOriginalId().get(0), + r._2()._2().getLegalname() != null ? r._2()._2().getLegalname().getValue() : "", + r._2()._2().getLegalshortname() != null ? r._2()._2().getLegalshortname().getValue() : "", + r._2()._2().getCountry() != null ? r._2()._2().getCountry().getClassid() : "", + r._2()._2().getWebsiteurl() != null ? r._2()._2().getWebsiteurl().getValue() : "", + r._2()._2().getCollectedfrom().get(0).getValue(), + "group::" + r._1()._1()), Encoders.bean(OrgSimRel.class)) .map( (MapFunction>) o -> new Tuple2<>(o.getLocal_id(), o), diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java index f5b2f48c5..dcbeb57f5 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java @@ -8,6 +8,7 @@ import java.util.Set; import java.util.stream.Collectors; import com.google.common.collect.Sets; + import eu.dnetlib.dhp.oa.dedup.IdGenerator; import eu.dnetlib.dhp.schema.common.EntityType; import eu.dnetlib.dhp.schema.oaf.KeyValue; diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java index 403498aeb..6089babf7 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java @@ -94,7 +94,8 @@ public class IdGeneratorTest { public void generateIdTest1() { String id1 = IdGenerator.generate(bestIds, "50|defaultID"); - System.out.println("id list 1 = " + bestIds.stream().map(i -> i.getPid().getValue()).collect(Collectors.toList())); + System.out + .println("id list 1 = " + bestIds.stream().map(i -> i.getPid().getValue()).collect(Collectors.toList())); assertEquals("50|dedup_wf_001::9c5cfbf993d38476e0f959a301239719", id1); } @@ -104,9 +105,11 @@ public class IdGeneratorTest { String id1 = IdGenerator.generate(bestIds2, "50|defaultID"); String id2 = IdGenerator.generate(bestIds3, "50|defaultID"); - System.out.println("id list 2 = " + bestIds2.stream().map(i -> i.getPid().getValue()).collect(Collectors.toList())); + System.out + .println("id list 2 = " + bestIds2.stream().map(i -> i.getPid().getValue()).collect(Collectors.toList())); System.out.println("winner 2 = " + id1); - System.out.println("id list 3 = " + bestIds3.stream().map(i -> i.getPid().getValue()).collect(Collectors.toList())); + System.out + .println("id list 3 = " + bestIds3.stream().map(i -> i.getPid().getValue()).collect(Collectors.toList())); System.out.println("winner 3 = " + id2); assertEquals("50|dedup_wf_001::2c56cc1914bffdb30fdff354e0099612", id1); diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java index b849160ff..c706061a0 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java @@ -1,12 +1,18 @@ package eu.dnetlib.dhp.oa.dedup; -import com.fasterxml.jackson.databind.ObjectMapper; -import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.schema.oaf.Relation; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import eu.dnetlib.pace.util.MapDocumentUtil; +import static java.nio.file.Files.createTempDirectory; + +import static org.apache.spark.sql.functions.count; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.lenient; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.net.URISyntaxException; +import java.nio.file.Paths; + import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; @@ -25,19 +31,16 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import eu.dnetlib.pace.util.MapDocumentUtil; import scala.Tuple2; -import java.io.File; -import java.io.IOException; -import java.io.Serializable; -import java.net.URISyntaxException; -import java.nio.file.Paths; - -import static java.nio.file.Files.createTempDirectory; -import static org.apache.spark.sql.functions.count; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.lenient; - @ExtendWith(MockitoExtension.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class SparkDedupTest implements Serializable { From 8e7f81c5f5dedf4b7095898f5ca4f62040ef4185 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 2 Nov 2020 14:25:00 +0100 Subject: [PATCH 020/445] code formatting --- .../actionmanager/project/httpconnector/HttpConnectorTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java index 9370efdb3..90b3919ed 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java @@ -9,7 +9,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; - @Disabled public class HttpConnectorTest { From 78c3c1b62bc31941fe9fc0d7f22ddd3a7c6c0fe8 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 2 Nov 2020 14:25:26 +0100 Subject: [PATCH 021/445] exclude pid values set to 'none' --- .../dhp/schema/oaf/utils/IdentifierFactory.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 3059459d6..c57a7c135 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -23,6 +23,7 @@ public class IdentifierFactory implements Serializable { public final static String ID_REGEX = "^[0-9][0-9]\\" + ID_PREFIX_SEPARATOR + ".{12}" + ID_SEPARATOR + "[a-zA-Z0-9]{32}$"; public static final int ID_PREFIX_LEN = 12; + public static final String NONE = "none"; /** * Creates an identifier from the most relevant PID (if available) in the given entity T. Returns entity.id @@ -40,15 +41,20 @@ public class IdentifierFactory implements Serializable { return entity .getPid() .stream() - .filter(s -> Objects.nonNull(s.getQualifier())) - .filter(s -> PidType.isValid(s.getQualifier().getClassid())) - .filter(s -> StringUtils.isNotBlank(StringUtils.trim(s.getValue()))) + .filter(s -> pidFilter(s)) .min(new PidComparator<>(entity)) .map(s -> idFromPid(entity, s)) .map(IdentifierFactory::verifyIdSyntax) .orElseGet(entity::getId); } + protected static boolean pidFilter(StructuredProperty s) { + return Objects.nonNull(s.getQualifier()) && + PidType.isValid(s.getQualifier().getClassid()) && + StringUtils.isNotBlank(StringUtils.trim(s.getValue())) && + !NONE.equals(StringUtils.trim(StringUtils.lowerCase(s.getValue()))); + } + /** * Utility method that normalises PID values on a per-type basis. * @param pid the PID whose value will be normalised. From 3fcd669e99cd3b42c23126cb842cd6cad6cea85c Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 3 Nov 2020 10:53:23 +0100 Subject: [PATCH 022/445] result merge operation leverage on custom ResultTypeComparator in the aggregator graph construction --- .../dhp/schema/oaf/ResultTypeComparator.java | 49 ++++++++++ .../raw/GenerateEntitiesApplication.java | 23 +++-- .../raw/GenerateEntitiesApplicationTest.java | 97 +++++++++++++++++++ .../eu/dnetlib/dhp/oa/graph/raw/oaf_orp.xml | 83 ++++++++++++++++ 4 files changed, 246 insertions(+), 6 deletions(-) create mode 100644 dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ResultTypeComparator.java create mode 100644 dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java create mode 100644 dhp-workflows/dhp-graph-mapper/src/test/resources/eu/dnetlib/dhp/oa/graph/raw/oaf_orp.xml diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ResultTypeComparator.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ResultTypeComparator.java new file mode 100644 index 000000000..11bc7297b --- /dev/null +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ResultTypeComparator.java @@ -0,0 +1,49 @@ +package eu.dnetlib.dhp.schema.oaf; + +import java.util.Comparator; + +import eu.dnetlib.dhp.schema.common.ModelConstants; + +public class ResultTypeComparator implements Comparator { + + @Override + public int compare(Result left, Result right) { + + if (left == null && right == null) + return 0; + if (left == null) + return 1; + if (right == null) + return -1; + + String lClass = left.getResulttype().getClassid(); + String rClass = right.getResulttype().getClassid(); + + if (lClass.equals(rClass)) + return 0; + + if (lClass.equals(ModelConstants.PUBLICATION_RESULTTYPE_CLASSID)) + return -1; + if (rClass.equals(ModelConstants.PUBLICATION_RESULTTYPE_CLASSID)) + return 1; + + if (lClass.equals(ModelConstants.DATASET_RESULTTYPE_CLASSID)) + return -1; + if (rClass.equals(ModelConstants.DATASET_RESULTTYPE_CLASSID)) + return 1; + + if (lClass.equals(ModelConstants.SOFTWARE_RESULTTYPE_CLASSID)) + return -1; + if (rClass.equals(ModelConstants.SOFTWARE_RESULTTYPE_CLASSID)) + return 1; + + if (lClass.equals(ModelConstants.ORP_RESULTTYPE_CLASSID)) + return -1; + if (rClass.equals(ModelConstants.ORP_RESULTTYPE_CLASSID)) + return 1; + + // Else (but unlikely), lexicographical ordering will do. + return lClass.compareTo(rClass); + } +} + diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java index 2dbe4eb83..27b57b2d1 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java @@ -4,11 +4,9 @@ package eu.dnetlib.dhp.oa.graph.raw; import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; @@ -20,6 +18,7 @@ import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; import org.apache.spark.sql.SparkSession; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -117,8 +116,7 @@ public class GenerateEntitiesApplication { if (ModelSupport.isSubClass(o1, OafEntity.class)) { if (ModelSupport.isSubClass(o1, Result.class)) { - // We cannot further specify the result type as different result types might share the same ID - ((Result) o1).mergeFrom((Result) o2); + return mergeResults((Result) o1, (Result) o2); } else if (ModelSupport.isSubClass(o1, Datasource.class)) { ((Datasource) o1).mergeFrom((Datasource) o2); } else if (ModelSupport.isSubClass(o1, Organization.class)) { @@ -136,6 +134,19 @@ public class GenerateEntitiesApplication { return o1; } + protected static Result mergeResults(Result o1, Result o2) { + Result r1 = o1; + Result r2 = o2; + + if (new ResultTypeComparator().compare(r1, r2) < 0) { + r1.mergeFrom(r2); + return r1; + } else { + r2.mergeFrom(r1); + return r2; + } + } + private static List convertToListOaf( final String id, final String s, diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java new file mode 100644 index 000000000..372d26076 --- /dev/null +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java @@ -0,0 +1,97 @@ +package eu.dnetlib.dhp.oa.graph.raw; + +import eu.dnetlib.dhp.oa.graph.clean.CleaningFunctionTest; +import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; +import eu.dnetlib.dhp.schema.common.ModelConstants; +import eu.dnetlib.dhp.schema.oaf.*; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.IOException; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.lenient; + +@ExtendWith(MockitoExtension.class) +public class GenerateEntitiesApplicationTest { + + @Mock + private ISLookUpService isLookUpService; + + @Mock + private VocabularyGroup vocs; + + @BeforeEach + public void setUp() throws IOException, ISLookUpException { + + lenient().when(isLookUpService.quickSearchProfile(VocabularyGroup.VOCABULARIES_XQUERY)).thenReturn(vocs()); + lenient() + .when(isLookUpService.quickSearchProfile(VocabularyGroup.VOCABULARY_SYNONYMS_XQUERY)) + .thenReturn(synonyms()); + + vocs = VocabularyGroup.loadVocsFromIS(isLookUpService); + } + + @Test + public void testMergeResult() throws IOException { + Result publication = getResult("oaf_record.xml", Publication.class); + Result dataset = getResult("odf_dataset.xml", Dataset.class); + Result software = getResult("odf_software.xml", Software.class); + Result orp = getResult("oaf_orp.xml", OtherResearchProduct.class); + + verifyMerge(publication, dataset, Publication.class, ModelConstants.PUBLICATION_RESULTTYPE_CLASSID); + verifyMerge(dataset, publication, Publication.class, ModelConstants.PUBLICATION_RESULTTYPE_CLASSID); + + verifyMerge(publication, software, Publication.class, ModelConstants.PUBLICATION_RESULTTYPE_CLASSID); + verifyMerge(software, publication, Publication.class, ModelConstants.PUBLICATION_RESULTTYPE_CLASSID); + + verifyMerge(publication, orp, Publication.class, ModelConstants.PUBLICATION_RESULTTYPE_CLASSID); + verifyMerge(orp, publication, Publication.class, ModelConstants.PUBLICATION_RESULTTYPE_CLASSID); + + verifyMerge(dataset, software, Dataset.class, ModelConstants.DATASET_RESULTTYPE_CLASSID); + verifyMerge(software, dataset, Dataset.class, ModelConstants.DATASET_RESULTTYPE_CLASSID); + + verifyMerge(dataset, orp, Dataset.class, ModelConstants.DATASET_RESULTTYPE_CLASSID); + verifyMerge(orp, dataset, Dataset.class, ModelConstants.DATASET_RESULTTYPE_CLASSID); + + verifyMerge(software, orp, Software.class, ModelConstants.SOFTWARE_RESULTTYPE_CLASSID); + verifyMerge(orp, software, Software.class, ModelConstants.SOFTWARE_RESULTTYPE_CLASSID); + } + + protected void verifyMerge(Result publication, Result dataset, Class clazz, String resultType) { + final Result merge = GenerateEntitiesApplication.mergeResults(publication, dataset); + assertTrue(clazz.isAssignableFrom(merge.getClass())); + assertEquals(resultType, merge.getResulttype().getClassid()); + } + + protected Result getResult(String xmlFileName, Class clazz) throws IOException { + final String xml = IOUtils.toString(getClass().getResourceAsStream(xmlFileName)); + return new OdfToOafMapper(vocs, false) + .processMdRecord(xml) + .stream() + .filter(s -> clazz.isAssignableFrom(s.getClass())) + .map(s -> (Result) s) + .findFirst() + .get(); + } + + + private List vocs() throws IOException { + return IOUtils + .readLines(CleaningFunctionTest.class.getResourceAsStream("/eu/dnetlib/dhp/oa/graph/clean/terms.txt")); + } + + private List synonyms() throws IOException { + return IOUtils + .readLines(CleaningFunctionTest.class.getResourceAsStream("/eu/dnetlib/dhp/oa/graph/clean/synonyms.txt")); + } + +} diff --git a/dhp-workflows/dhp-graph-mapper/src/test/resources/eu/dnetlib/dhp/oa/graph/raw/oaf_orp.xml b/dhp-workflows/dhp-graph-mapper/src/test/resources/eu/dnetlib/dhp/oa/graph/raw/oaf_orp.xml new file mode 100644 index 000000000..c6b5d7b2e --- /dev/null +++ b/dhp-workflows/dhp-graph-mapper/src/test/resources/eu/dnetlib/dhp/oa/graph/raw/oaf_orp.xml @@ -0,0 +1,83 @@ + + +

+ pensoft_____::00ea4a1cd53806a97d62ea6bf268f2a2 + 10.3897/oneeco.2.e13718 + + + + + + 2020-03-23T00:20:51.392Z + 2020-03-23T00:26:59.078Z + pensoft_____ +
+ + Ecosystem Service capacity is higher in areas of multiple designation types + Nikolaidou,Charitini + Votsi,Nefta + Sgardelis,Steanos + Halley,John + Pantis,John + Tsiafouli,Maria + 2017 + The implementation of the Ecosystem Service (ES) concept into practice might be a challenging task as it has to take into account previous “traditional” policies and approaches that have evaluated nature and biodiversity differently. Among them the Habitat (92/43/EC) and Bird Directives (79/409/EC), the Water Framework Directive (2000/60/EC), and the Noise Directive (2002/49/EC) have led to the evaluation/designation of areas in Europe with different criteria. In this study our goal was to understand how the ES capacity of an area is related to its designation and if areas with multiple designations have higher capacity in providing ES. We selected four catchments in Greece with a great variety of characteristics covering over 25% of the national territory. Inside the catchments we assessed the ES capacity (following the methodology of Burkhard et al. 2009) of areas designated as Natura 2000 sites, Quiet areas and Wetlands or Water bodies and found those areas that have multiple designations. Data were analyzed by GLM to reveal differences regarding the ES capacity among the different types of areas. We also investigated by PCA synergies and trade-offs among different kinds of ES and tested for correlations among landscape properties, such as elevation, aspect and slope and the ES potential. Our results show that areas with different types or multiple designations have a different capacity in providing ES. Areas of one designation type (Protected or Quiet Areas) had in general intermediate scores in most ES but scores were higher compared to areas with no designation, which displayed stronger capacity in provisioning services. Among Protected Areas and Quiet Areas the latter scored better in general. Areas that combined both designation types (Protected and Quiet Areas) showed the highest capacity in 13 out of 29 ES, that were mostly linked with natural and forest ecosystems. We found significant synergies among most regulating, supporting and cultural ES which in turn display trade-offs with provisioning services. The different ES are spatially related and display strong correlation with landscape properties, such as elevation and slope. We suggest that the designation status of an area can be used as an alternative tool for environmental policy, indicating the capacity for ES provision. Multiple designations of areas can be used as proxies for locating ES “hotspots”. This integration of “traditional” evaluation and designation and the “newer” ES concept forms a time- and cost-effective way to be adopted by stakeholders and policy-makers in order to start complying with new standards and demands for nature conservation and environmental management. + text/html + https://doi.org/10.3897/oneeco.2.e13718 + https://oneecosystem.pensoft.net/article/13718/ + eng + Pensoft Publishers + info:eu-repo/semantics/altIdentifier/eissn/2367-8194 + info:eu-repo/grantAgreement/EC/FP7/226852 + One Ecosystem 2: e13718 + One Ecosystem 2: e13718 + One Ecosystem 2: e13718 + Ecosystem Services hotspots + Natura 2000 + Quiet Protected Areas + Biodiversity + Agriculture + Elevation + Slope + Ecosystem Service trade-offs and synergies + cultural services + provisioning services + regulating services + supporting services + Research Artefact + 0020 + 2017-01-01 + corda_______::226852 + OPEN + + + 10.3897/oneeco.2.e13718 + https://oneecosystem.pensoft.net/article/13718/ + One Ecosystem + 0001 + + + + + http%3A%2F%2Fzookeys.pensoft.net%2Foai.php + 10.3897/oneeco.2.e13718 + 2017-09-08 + http://www.openarchives.org/OAI/2.0/oai_dc/ + + + + false + false + 0.9 + + + + + \ No newline at end of file From 86d6fbe95b177c28a0952685abd5f1fee0232d46 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 3 Nov 2020 12:19:46 +0100 Subject: [PATCH 023/445] refactoring: CleaningFunctions and OafMapperUtils moved in dhp-commong --- .../dhp/schema/oaf}/CleaningFunctions.java | 56 +++++--- .../dhp/schema/oaf}/OafMapperUtils.java | 53 +++++-- .../dhp/schema/oaf/ResultTypeComparator.java | 64 ++++----- .../schema/oaf/utils/IdentifierFactory.java | 31 +--- .../oa/graph/clean/CleanGraphSparkJob.java | 7 - .../raw/AbstractMdRecordToOafMapper.java | 36 +---- .../raw/MigrateDbEntitiesApplication.java | 22 ++- .../dhp/oa/graph/raw/OafToOafMapper.java | 9 +- .../dhp/oa/graph/raw/OdfToOafMapper.java | 11 +- .../dhp/oa/graph/raw/common/Vocabulary.java | 1 + .../oa/graph/raw/common/VocabularyGroup.java | 1 + .../oa/graph/clean/CleaningFunctionTest.java | 2 - .../raw/GenerateEntitiesApplicationTest.java | 136 +++++++++--------- .../raw/MigrateDbEntitiesApplicationTest.java | 2 +- 14 files changed, 203 insertions(+), 228 deletions(-) rename {dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean => dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf}/CleaningFunctions.java (81%) rename {dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common => dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf}/OafMapperUtils.java (84%) diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java similarity index 81% rename from dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctions.java rename to dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index 12543d8c7..1f2b47cfb 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -1,8 +1,9 @@ -package eu.dnetlib.dhp.oa.graph.clean; +package eu.dnetlib.dhp.schema.oaf; import java.util.LinkedHashMap; import java.util.Objects; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; @@ -10,13 +11,13 @@ import org.apache.commons.lang3.StringUtils; import com.clearspring.analytics.util.Lists; -import eu.dnetlib.dhp.oa.graph.raw.AbstractMdRecordToOafMapper; -import eu.dnetlib.dhp.oa.graph.raw.common.OafMapperUtils; import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.*; +import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory; public class CleaningFunctions { + public static final String DOI_URL_PREFIX = "^http(s?):\\/\\/(dx\\.)?doi\\.org\\/"; public static final String ORCID_PREFIX_REGEX = "^http(s?):\\/\\/orcid\\.org\\/"; public static final String NONE = "none"; @@ -72,7 +73,7 @@ public class CleaningFunctions { return value; } - protected static T fixDefaults(T value) { + public static T fixDefaults(T value) { if (value instanceof Datasource) { // nothing to clean here } else if (value instanceof Project) { @@ -109,20 +110,17 @@ public class CleaningFunctions { } if (Objects.nonNull(r.getPid())) { r - .setPid( - r - .getPid() - .stream() - .filter(Objects::nonNull) - .filter(sp -> StringUtils.isNotBlank(StringUtils.trim(sp.getValue()))) - .filter(sp -> NONE.equalsIgnoreCase(sp.getValue())) - .filter(sp -> Objects.nonNull(sp.getQualifier())) - .filter(sp -> StringUtils.isNotBlank(sp.getQualifier().getClassid())) - .map(sp -> { - sp.setValue(StringUtils.trim(sp.getValue())); - return sp; - }) - .collect(Collectors.toList())); + .setPid( + r + .getPid() + .stream() + .filter(Objects::nonNull) + .filter(sp -> StringUtils.isNotBlank(StringUtils.trim(sp.getValue()))) + .filter(sp -> NONE.equalsIgnoreCase(sp.getValue())) + .filter(sp -> Objects.nonNull(sp.getQualifier())) + .filter(sp -> StringUtils.isNotBlank(sp.getQualifier().getClassid())) + .map(CleaningFunctions::normalizePidValue) + .collect(Collectors.toList())); } if (Objects.isNull(r.getResourcetype()) || StringUtils.isBlank(r.getResourcetype().getClassid())) { r @@ -143,7 +141,7 @@ public class CleaningFunctions { } } if (Objects.isNull(r.getBestaccessright()) || StringUtils.isBlank(r.getBestaccessright().getClassid())) { - Qualifier bestaccessrights = AbstractMdRecordToOafMapper.createBestAccessRights(r.getInstance()); + Qualifier bestaccessrights = OafMapperUtils.createBestAccessRights(r.getInstance()); if (Objects.isNull(bestaccessrights)) { r .setBestaccessright( @@ -219,4 +217,24 @@ public class CleaningFunctions { classid, classname, scheme, scheme); } + /** + * Utility method that normalises PID values on a per-type basis. + * @param pid the PID whose value will be normalised. + * @return the PID containing the normalised value. + */ + public static StructuredProperty normalizePidValue(StructuredProperty pid) { + String value = Optional + .ofNullable(pid.getValue()) + .map(String::trim) + .orElseThrow(() -> new IllegalArgumentException("PID value cannot be empty")); + switch (pid.getQualifier().getClassid()) { + + // TODO add cleaning for more PID types as needed + case "doi": + pid.setValue(value.toLowerCase().replaceAll(DOI_URL_PREFIX, "")); + break; + } + return pid; + } + } diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/OafMapperUtils.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java similarity index 84% rename from dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/OafMapperUtils.java rename to dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java index 84b29e3d4..f079c55af 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/OafMapperUtils.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java @@ -1,11 +1,10 @@ -package eu.dnetlib.dhp.oa.graph.raw.common; +package eu.dnetlib.dhp.schema.oaf; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import static eu.dnetlib.dhp.schema.common.ModelConstants.*; +import static eu.dnetlib.dhp.schema.common.ModelConstants.DNET_ACCESS_MODES; + +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Predicate; @@ -13,15 +12,7 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; -import eu.dnetlib.dhp.schema.oaf.DataInfo; -import eu.dnetlib.dhp.schema.oaf.ExtraInfo; -import eu.dnetlib.dhp.schema.oaf.Field; -import eu.dnetlib.dhp.schema.oaf.Journal; -import eu.dnetlib.dhp.schema.oaf.KeyValue; -import eu.dnetlib.dhp.schema.oaf.OAIProvenance; -import eu.dnetlib.dhp.schema.oaf.OriginDescription; -import eu.dnetlib.dhp.schema.oaf.Qualifier; -import eu.dnetlib.dhp.schema.oaf.StructuredProperty; +import eu.dnetlib.dhp.schema.common.LicenseComparator; import eu.dnetlib.dhp.utils.DHPUtils; public class OafMapperUtils { @@ -270,4 +261,36 @@ public class OafMapperUtils { final Map seen = new ConcurrentHashMap<>(); return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; } + + public static Qualifier createBestAccessRights(final List instanceList) { + return getBestAccessRights(instanceList); + } + + protected static Qualifier getBestAccessRights(final List instanceList) { + if (instanceList != null) { + final Optional min = instanceList + .stream() + .map(i -> i.getAccessright()) + .min(new LicenseComparator()); + + final Qualifier rights = min.isPresent() ? min.get() : new Qualifier(); + + if (StringUtils.isBlank(rights.getClassid())) { + rights.setClassid(UNKNOWN); + } + if (StringUtils.isBlank(rights.getClassname()) + || UNKNOWN.equalsIgnoreCase(rights.getClassname())) { + rights.setClassname(NOT_AVAILABLE); + } + if (StringUtils.isBlank(rights.getSchemeid())) { + rights.setSchemeid(DNET_ACCESS_MODES); + } + if (StringUtils.isBlank(rights.getSchemename())) { + rights.setSchemename(DNET_ACCESS_MODES); + } + + return rights; + } + return null; + } } diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ResultTypeComparator.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ResultTypeComparator.java index 11bc7297b..6c11d1a85 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ResultTypeComparator.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ResultTypeComparator.java @@ -1,3 +1,4 @@ + package eu.dnetlib.dhp.schema.oaf; import java.util.Comparator; @@ -6,44 +7,43 @@ import eu.dnetlib.dhp.schema.common.ModelConstants; public class ResultTypeComparator implements Comparator { - @Override - public int compare(Result left, Result right) { + @Override + public int compare(Result left, Result right) { - if (left == null && right == null) - return 0; - if (left == null) - return 1; - if (right == null) - return -1; + if (left == null && right == null) + return 0; + if (left == null) + return 1; + if (right == null) + return -1; - String lClass = left.getResulttype().getClassid(); - String rClass = right.getResulttype().getClassid(); + String lClass = left.getResulttype().getClassid(); + String rClass = right.getResulttype().getClassid(); - if (lClass.equals(rClass)) - return 0; + if (lClass.equals(rClass)) + return 0; - if (lClass.equals(ModelConstants.PUBLICATION_RESULTTYPE_CLASSID)) - return -1; - if (rClass.equals(ModelConstants.PUBLICATION_RESULTTYPE_CLASSID)) - return 1; + if (lClass.equals(ModelConstants.PUBLICATION_RESULTTYPE_CLASSID)) + return -1; + if (rClass.equals(ModelConstants.PUBLICATION_RESULTTYPE_CLASSID)) + return 1; - if (lClass.equals(ModelConstants.DATASET_RESULTTYPE_CLASSID)) - return -1; - if (rClass.equals(ModelConstants.DATASET_RESULTTYPE_CLASSID)) - return 1; + if (lClass.equals(ModelConstants.DATASET_RESULTTYPE_CLASSID)) + return -1; + if (rClass.equals(ModelConstants.DATASET_RESULTTYPE_CLASSID)) + return 1; - if (lClass.equals(ModelConstants.SOFTWARE_RESULTTYPE_CLASSID)) - return -1; - if (rClass.equals(ModelConstants.SOFTWARE_RESULTTYPE_CLASSID)) - return 1; + if (lClass.equals(ModelConstants.SOFTWARE_RESULTTYPE_CLASSID)) + return -1; + if (rClass.equals(ModelConstants.SOFTWARE_RESULTTYPE_CLASSID)) + return 1; - if (lClass.equals(ModelConstants.ORP_RESULTTYPE_CLASSID)) - return -1; - if (rClass.equals(ModelConstants.ORP_RESULTTYPE_CLASSID)) - return 1; + if (lClass.equals(ModelConstants.ORP_RESULTTYPE_CLASSID)) + return -1; + if (rClass.equals(ModelConstants.ORP_RESULTTYPE_CLASSID)) + return 1; - // Else (but unlikely), lexicographical ordering will do. - return lClass.compareTo(rClass); - } + // Else (but unlikely), lexicographical ordering will do. + return lClass.compareTo(rClass); + } } - diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index c57a7c135..6692b4223 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -7,6 +7,7 @@ import java.util.Optional; import org.apache.commons.lang.StringUtils; +import eu.dnetlib.dhp.schema.oaf.CleaningFunctions; import eu.dnetlib.dhp.schema.oaf.OafEntity; import eu.dnetlib.dhp.schema.oaf.StructuredProperty; import eu.dnetlib.dhp.utils.DHPUtils; @@ -16,8 +17,6 @@ import eu.dnetlib.dhp.utils.DHPUtils; */ public class IdentifierFactory implements Serializable { - public static final String DOI_URL_PREFIX = "^http(s?):\\/\\/(dx\\.)?doi\\.org\\/"; - public static final String ID_SEPARATOR = "::"; public static final String ID_PREFIX_SEPARATOR = "|"; public final static String ID_REGEX = "^[0-9][0-9]\\" + ID_PREFIX_SEPARATOR + ".{12}" + ID_SEPARATOR @@ -50,29 +49,9 @@ public class IdentifierFactory implements Serializable { protected static boolean pidFilter(StructuredProperty s) { return Objects.nonNull(s.getQualifier()) && - PidType.isValid(s.getQualifier().getClassid()) && - StringUtils.isNotBlank(StringUtils.trim(s.getValue())) && - !NONE.equals(StringUtils.trim(StringUtils.lowerCase(s.getValue()))); - } - - /** - * Utility method that normalises PID values on a per-type basis. - * @param pid the PID whose value will be normalised. - * @return the PID containing the normalised value. - */ - public static StructuredProperty normalizePidValue(StructuredProperty pid) { - String value = Optional - .ofNullable(pid.getValue()) - .map(String::trim) - .orElseThrow(() -> new IllegalArgumentException("PID value cannot be empty")); - switch (pid.getQualifier().getClassid()) { - - // TODO add cleaning for more PID types as needed - case "doi": - pid.setValue(value.toLowerCase().replaceAll(DOI_URL_PREFIX, "")); - break; - } - return pid; + PidType.isValid(s.getQualifier().getClassid()) && + StringUtils.isNotBlank(StringUtils.trim(s.getValue())) && + !NONE.equals(StringUtils.trim(StringUtils.lowerCase(s.getValue()))); } private static String verifyIdSyntax(String s) { @@ -89,7 +68,7 @@ public class IdentifierFactory implements Serializable { .append(ID_PREFIX_SEPARATOR) .append(createPrefix(s.getQualifier().getClassid())) .append(ID_SEPARATOR) - .append(DHPUtils.md5(normalizePidValue(s).getValue())) + .append(DHPUtils.md5(CleaningFunctions.normalizePidValue(s).getValue())) .toString(); } diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java index e295b9503..a15c2ee62 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java @@ -3,13 +3,9 @@ package eu.dnetlib.dhp.oa.graph.clean; import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; -import java.io.BufferedInputStream; -import java.util.Objects; import java.util.Optional; -import java.util.stream.Collectors; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.spark.SparkConf; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.sql.Dataset; @@ -23,10 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.common.HdfsSupport; -import eu.dnetlib.dhp.oa.graph.raw.AbstractMdRecordToOafMapper; -import eu.dnetlib.dhp.oa.graph.raw.common.OafMapperUtils; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; -import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java index 57e0aa49e..051225857 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java @@ -1,8 +1,8 @@ package eu.dnetlib.dhp.oa.graph.raw; -import static eu.dnetlib.dhp.oa.graph.raw.common.OafMapperUtils.*; import static eu.dnetlib.dhp.schema.common.ModelConstants.*; +import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.*; import java.util.*; @@ -282,7 +282,7 @@ public abstract class AbstractMdRecordToOafMapper { r.setExternalReference(new ArrayList<>()); // NOT PRESENT IN MDSTORES r.setInstance(instances); - r.setBestaccessright(getBestAccessRights(instances)); + r.setBestaccessright(OafMapperUtils.createBestAccessRights(instances)); } protected abstract List prepareResultPids(Document doc, DataInfo info); @@ -367,38 +367,6 @@ public abstract class AbstractMdRecordToOafMapper { protected abstract Field prepareDatasetStorageDate(Document doc, DataInfo info); - public static Qualifier createBestAccessRights(final List instanceList) { - return getBestAccessRights(instanceList); - } - - protected static Qualifier getBestAccessRights(final List instanceList) { - if (instanceList != null) { - final Optional min = instanceList - .stream() - .map(i -> i.getAccessright()) - .min(new LicenseComparator()); - - final Qualifier rights = min.isPresent() ? min.get() : new Qualifier(); - - if (StringUtils.isBlank(rights.getClassid())) { - rights.setClassid(UNKNOWN); - } - if (StringUtils.isBlank(rights.getClassname()) - || UNKNOWN.equalsIgnoreCase(rights.getClassname())) { - rights.setClassname(NOT_AVAILABLE); - } - if (StringUtils.isBlank(rights.getSchemeid())) { - rights.setSchemeid(DNET_ACCESS_MODES); - } - if (StringUtils.isBlank(rights.getSchemename())) { - rights.setSchemename(DNET_ACCESS_MODES); - } - - return rights; - } - return null; - } - private Journal prepareJournal(final Document doc, final DataInfo info) { final Node n = doc.selectSingleNode("//oaf:journal"); if (n != null) { diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java index 30fdd17e9..16b0aa80b 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java @@ -1,16 +1,6 @@ package eu.dnetlib.dhp.oa.graph.raw; -import static eu.dnetlib.dhp.oa.graph.raw.common.OafMapperUtils.asString; -import static eu.dnetlib.dhp.oa.graph.raw.common.OafMapperUtils.createOpenaireId; -import static eu.dnetlib.dhp.oa.graph.raw.common.OafMapperUtils.dataInfo; -import static eu.dnetlib.dhp.oa.graph.raw.common.OafMapperUtils.field; -import static eu.dnetlib.dhp.oa.graph.raw.common.OafMapperUtils.journal; -import static eu.dnetlib.dhp.oa.graph.raw.common.OafMapperUtils.listFields; -import static eu.dnetlib.dhp.oa.graph.raw.common.OafMapperUtils.listKeyValues; -import static eu.dnetlib.dhp.oa.graph.raw.common.OafMapperUtils.qualifier; -import static eu.dnetlib.dhp.oa.graph.raw.common.OafMapperUtils.structuredProperty; -import static eu.dnetlib.dhp.schema.common.ModelConstants.DATASET_DEFAULT_RESULTTYPE; import static eu.dnetlib.dhp.schema.common.ModelConstants.DATASOURCE_ORGANIZATION; import static eu.dnetlib.dhp.schema.common.ModelConstants.DNET_PROVENANCE_ACTIONS; import static eu.dnetlib.dhp.schema.common.ModelConstants.ENTITYREGISTRY_PROVENANCE_ACTION; @@ -19,19 +9,25 @@ import static eu.dnetlib.dhp.schema.common.ModelConstants.IS_PARTICIPANT; import static eu.dnetlib.dhp.schema.common.ModelConstants.IS_PRODUCED_BY; import static eu.dnetlib.dhp.schema.common.ModelConstants.IS_PROVIDED_BY; import static eu.dnetlib.dhp.schema.common.ModelConstants.IS_RELATED_TO; -import static eu.dnetlib.dhp.schema.common.ModelConstants.ORP_DEFAULT_RESULTTYPE; import static eu.dnetlib.dhp.schema.common.ModelConstants.OUTCOME; import static eu.dnetlib.dhp.schema.common.ModelConstants.PARTICIPATION; import static eu.dnetlib.dhp.schema.common.ModelConstants.PRODUCES; import static eu.dnetlib.dhp.schema.common.ModelConstants.PROJECT_ORGANIZATION; import static eu.dnetlib.dhp.schema.common.ModelConstants.PROVIDES; import static eu.dnetlib.dhp.schema.common.ModelConstants.PROVISION; -import static eu.dnetlib.dhp.schema.common.ModelConstants.PUBLICATION_DEFAULT_RESULTTYPE; import static eu.dnetlib.dhp.schema.common.ModelConstants.RELATIONSHIP; import static eu.dnetlib.dhp.schema.common.ModelConstants.RESULT_PROJECT; import static eu.dnetlib.dhp.schema.common.ModelConstants.RESULT_RESULT; -import static eu.dnetlib.dhp.schema.common.ModelConstants.SOFTWARE_DEFAULT_RESULTTYPE; import static eu.dnetlib.dhp.schema.common.ModelConstants.USER_CLAIM; +import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.asString; +import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.createOpenaireId; +import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.dataInfo; +import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.field; +import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.journal; +import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.listFields; +import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.listKeyValues; +import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.qualifier; +import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.structuredProperty; import java.io.Closeable; import java.io.IOException; diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java index 4813a202b..a1798e4fb 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java @@ -1,14 +1,13 @@ package eu.dnetlib.dhp.oa.graph.raw; -import static eu.dnetlib.dhp.oa.graph.raw.common.OafMapperUtils.createOpenaireId; -import static eu.dnetlib.dhp.oa.graph.raw.common.OafMapperUtils.field; -import static eu.dnetlib.dhp.oa.graph.raw.common.OafMapperUtils.structuredProperty; import static eu.dnetlib.dhp.schema.common.ModelConstants.*; +import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.createOpenaireId; +import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.field; +import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.structuredProperty; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; @@ -293,7 +292,7 @@ public class OafToOafMapper extends AbstractMdRecordToOafMapper { return prepareListStructPropsWithValidQualifier( doc, "//oaf:identifier", "@identifierType", DNET_PID_TYPES, info) .stream() - .map(IdentifierFactory::normalizePidValue) + .map(CleaningFunctions::normalizePidValue) .collect(Collectors.toList()); } diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java index d819de1cb..b7ee2d546 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java @@ -1,10 +1,10 @@ package eu.dnetlib.dhp.oa.graph.raw; -import static eu.dnetlib.dhp.oa.graph.raw.common.OafMapperUtils.createOpenaireId; -import static eu.dnetlib.dhp.oa.graph.raw.common.OafMapperUtils.field; -import static eu.dnetlib.dhp.oa.graph.raw.common.OafMapperUtils.structuredProperty; import static eu.dnetlib.dhp.schema.common.ModelConstants.*; +import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.createOpenaireId; +import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.field; +import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.structuredProperty; import java.util.ArrayList; import java.util.Arrays; @@ -17,11 +17,8 @@ import org.apache.commons.lang3.StringUtils; import org.dom4j.Document; import org.dom4j.Node; -import com.google.common.collect.Lists; - import eu.dnetlib.dhp.common.PacePerson; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; -import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory; @@ -382,7 +379,7 @@ public class OdfToOafMapper extends AbstractMdRecordToOafMapper { return res .stream() - .map(IdentifierFactory::normalizePidValue) + .map(CleaningFunctions::normalizePidValue) .collect(Collectors.toList()); } diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/Vocabulary.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/Vocabulary.java index 9bf198c8b..bfc4fd6f1 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/Vocabulary.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/Vocabulary.java @@ -10,6 +10,7 @@ import org.apache.commons.lang3.StringUtils; import com.google.common.collect.Maps; +import eu.dnetlib.dhp.schema.oaf.OafMapperUtils; import eu.dnetlib.dhp.schema.oaf.Qualifier; public class Vocabulary implements Serializable { diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/VocabularyGroup.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/VocabularyGroup.java index 334339d3b..32452bdc5 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/VocabularyGroup.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/VocabularyGroup.java @@ -7,6 +7,7 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import eu.dnetlib.dhp.schema.oaf.OafMapperUtils; import eu.dnetlib.dhp.schema.oaf.Qualifier; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java index 8a53c3a50..5ad8f2ac7 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java @@ -7,8 +7,6 @@ import static org.mockito.Mockito.lenient; import java.io.IOException; import java.util.List; import java.util.Set; -import java.util.function.Predicate; -import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.io.IOUtils; diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java index 372d26076..a028738ea 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java @@ -1,11 +1,13 @@ + package eu.dnetlib.dhp.oa.graph.raw; -import eu.dnetlib.dhp.oa.graph.clean.CleaningFunctionTest; -import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; -import eu.dnetlib.dhp.schema.common.ModelConstants; -import eu.dnetlib.dhp.schema.oaf.*; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.lenient; + +import java.io.IOException; +import java.util.List; + import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -13,85 +15,85 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.io.IOException; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.lenient; +import eu.dnetlib.dhp.oa.graph.clean.CleaningFunctionTest; +import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; +import eu.dnetlib.dhp.schema.common.ModelConstants; +import eu.dnetlib.dhp.schema.oaf.*; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; @ExtendWith(MockitoExtension.class) public class GenerateEntitiesApplicationTest { - @Mock - private ISLookUpService isLookUpService; + @Mock + private ISLookUpService isLookUpService; - @Mock - private VocabularyGroup vocs; + @Mock + private VocabularyGroup vocs; - @BeforeEach - public void setUp() throws IOException, ISLookUpException { + @BeforeEach + public void setUp() throws IOException, ISLookUpException { - lenient().when(isLookUpService.quickSearchProfile(VocabularyGroup.VOCABULARIES_XQUERY)).thenReturn(vocs()); - lenient() - .when(isLookUpService.quickSearchProfile(VocabularyGroup.VOCABULARY_SYNONYMS_XQUERY)) - .thenReturn(synonyms()); + lenient().when(isLookUpService.quickSearchProfile(VocabularyGroup.VOCABULARIES_XQUERY)).thenReturn(vocs()); + lenient() + .when(isLookUpService.quickSearchProfile(VocabularyGroup.VOCABULARY_SYNONYMS_XQUERY)) + .thenReturn(synonyms()); - vocs = VocabularyGroup.loadVocsFromIS(isLookUpService); - } + vocs = VocabularyGroup.loadVocsFromIS(isLookUpService); + } - @Test - public void testMergeResult() throws IOException { - Result publication = getResult("oaf_record.xml", Publication.class); - Result dataset = getResult("odf_dataset.xml", Dataset.class); - Result software = getResult("odf_software.xml", Software.class); - Result orp = getResult("oaf_orp.xml", OtherResearchProduct.class); + @Test + public void testMergeResult() throws IOException { + Result publication = getResult("oaf_record.xml", Publication.class); + Result dataset = getResult("odf_dataset.xml", Dataset.class); + Result software = getResult("odf_software.xml", Software.class); + Result orp = getResult("oaf_orp.xml", OtherResearchProduct.class); - verifyMerge(publication, dataset, Publication.class, ModelConstants.PUBLICATION_RESULTTYPE_CLASSID); - verifyMerge(dataset, publication, Publication.class, ModelConstants.PUBLICATION_RESULTTYPE_CLASSID); + verifyMerge(publication, dataset, Publication.class, ModelConstants.PUBLICATION_RESULTTYPE_CLASSID); + verifyMerge(dataset, publication, Publication.class, ModelConstants.PUBLICATION_RESULTTYPE_CLASSID); - verifyMerge(publication, software, Publication.class, ModelConstants.PUBLICATION_RESULTTYPE_CLASSID); - verifyMerge(software, publication, Publication.class, ModelConstants.PUBLICATION_RESULTTYPE_CLASSID); + verifyMerge(publication, software, Publication.class, ModelConstants.PUBLICATION_RESULTTYPE_CLASSID); + verifyMerge(software, publication, Publication.class, ModelConstants.PUBLICATION_RESULTTYPE_CLASSID); - verifyMerge(publication, orp, Publication.class, ModelConstants.PUBLICATION_RESULTTYPE_CLASSID); - verifyMerge(orp, publication, Publication.class, ModelConstants.PUBLICATION_RESULTTYPE_CLASSID); + verifyMerge(publication, orp, Publication.class, ModelConstants.PUBLICATION_RESULTTYPE_CLASSID); + verifyMerge(orp, publication, Publication.class, ModelConstants.PUBLICATION_RESULTTYPE_CLASSID); - verifyMerge(dataset, software, Dataset.class, ModelConstants.DATASET_RESULTTYPE_CLASSID); - verifyMerge(software, dataset, Dataset.class, ModelConstants.DATASET_RESULTTYPE_CLASSID); + verifyMerge(dataset, software, Dataset.class, ModelConstants.DATASET_RESULTTYPE_CLASSID); + verifyMerge(software, dataset, Dataset.class, ModelConstants.DATASET_RESULTTYPE_CLASSID); - verifyMerge(dataset, orp, Dataset.class, ModelConstants.DATASET_RESULTTYPE_CLASSID); - verifyMerge(orp, dataset, Dataset.class, ModelConstants.DATASET_RESULTTYPE_CLASSID); + verifyMerge(dataset, orp, Dataset.class, ModelConstants.DATASET_RESULTTYPE_CLASSID); + verifyMerge(orp, dataset, Dataset.class, ModelConstants.DATASET_RESULTTYPE_CLASSID); - verifyMerge(software, orp, Software.class, ModelConstants.SOFTWARE_RESULTTYPE_CLASSID); - verifyMerge(orp, software, Software.class, ModelConstants.SOFTWARE_RESULTTYPE_CLASSID); - } + verifyMerge(software, orp, Software.class, ModelConstants.SOFTWARE_RESULTTYPE_CLASSID); + verifyMerge(orp, software, Software.class, ModelConstants.SOFTWARE_RESULTTYPE_CLASSID); + } - protected void verifyMerge(Result publication, Result dataset, Class clazz, String resultType) { - final Result merge = GenerateEntitiesApplication.mergeResults(publication, dataset); - assertTrue(clazz.isAssignableFrom(merge.getClass())); - assertEquals(resultType, merge.getResulttype().getClassid()); - } + protected void verifyMerge(Result publication, Result dataset, Class clazz, + String resultType) { + final Result merge = GenerateEntitiesApplication.mergeResults(publication, dataset); + assertTrue(clazz.isAssignableFrom(merge.getClass())); + assertEquals(resultType, merge.getResulttype().getClassid()); + } - protected Result getResult(String xmlFileName, Class clazz) throws IOException { - final String xml = IOUtils.toString(getClass().getResourceAsStream(xmlFileName)); - return new OdfToOafMapper(vocs, false) - .processMdRecord(xml) - .stream() - .filter(s -> clazz.isAssignableFrom(s.getClass())) - .map(s -> (Result) s) - .findFirst() - .get(); - } + protected Result getResult(String xmlFileName, Class clazz) throws IOException { + final String xml = IOUtils.toString(getClass().getResourceAsStream(xmlFileName)); + return new OdfToOafMapper(vocs, false) + .processMdRecord(xml) + .stream() + .filter(s -> clazz.isAssignableFrom(s.getClass())) + .map(s -> (Result) s) + .findFirst() + .get(); + } + private List vocs() throws IOException { + return IOUtils + .readLines(CleaningFunctionTest.class.getResourceAsStream("/eu/dnetlib/dhp/oa/graph/clean/terms.txt")); + } - private List vocs() throws IOException { - return IOUtils - .readLines(CleaningFunctionTest.class.getResourceAsStream("/eu/dnetlib/dhp/oa/graph/clean/terms.txt")); - } - - private List synonyms() throws IOException { - return IOUtils - .readLines(CleaningFunctionTest.class.getResourceAsStream("/eu/dnetlib/dhp/oa/graph/clean/synonyms.txt")); - } + private List synonyms() throws IOException { + return IOUtils + .readLines(CleaningFunctionTest.class.getResourceAsStream("/eu/dnetlib/dhp/oa/graph/clean/synonyms.txt")); + } } diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplicationTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplicationTest.java index f663d6095..3c0e2ce8e 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplicationTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplicationTest.java @@ -27,10 +27,10 @@ import org.mockito.junit.jupiter.MockitoExtension; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import eu.dnetlib.dhp.oa.graph.raw.common.OafMapperUtils; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; import eu.dnetlib.dhp.schema.oaf.Datasource; import eu.dnetlib.dhp.schema.oaf.Oaf; +import eu.dnetlib.dhp.schema.oaf.OafMapperUtils; import eu.dnetlib.dhp.schema.oaf.Organization; import eu.dnetlib.dhp.schema.oaf.Project; import eu.dnetlib.dhp.schema.oaf.Relation; From ea2a0ea949adee41ac7595e0b54f8b7b574ba9da Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 3 Nov 2020 18:43:37 +0100 Subject: [PATCH 024/445] IdentifierFactory considers only DOIs matching a given regex --- .../dhp/schema/oaf/CleaningFunctions.java | 6 ++--- .../schema/oaf/utils/IdentifierFactory.java | 26 ++++++++++++++++--- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index 1f2b47cfb..12fbcc490 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -12,12 +12,10 @@ import org.apache.commons.lang3.StringUtils; import com.clearspring.analytics.util.Lists; import eu.dnetlib.dhp.schema.common.ModelConstants; -import eu.dnetlib.dhp.schema.oaf.*; -import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory; public class CleaningFunctions { - public static final String DOI_URL_PREFIX = "^http(s?):\\/\\/(dx\\.)?doi\\.org\\/"; + public static final String DOI_URL_PREFIX_REGEX = "(^http(s?):\\/\\/)(((dx\\.)?doi\\.org)|(handle\\.test\\.datacite\\.org))\\/"; public static final String ORCID_PREFIX_REGEX = "^http(s?):\\/\\/orcid\\.org\\/"; public static final String NONE = "none"; @@ -231,7 +229,7 @@ public class CleaningFunctions { // TODO add cleaning for more PID types as needed case "doi": - pid.setValue(value.toLowerCase().replaceAll(DOI_URL_PREFIX, "")); + pid.setValue(value.toLowerCase().replaceAll(DOI_URL_PREFIX_REGEX, "")); break; } return pid; diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 6692b4223..319cda0bf 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -4,6 +4,7 @@ package eu.dnetlib.dhp.schema.oaf.utils; import java.io.Serializable; import java.util.Objects; import java.util.Optional; +import java.util.regex.Pattern; import org.apache.commons.lang.StringUtils; @@ -21,6 +22,12 @@ public class IdentifierFactory implements Serializable { public static final String ID_PREFIX_SEPARATOR = "|"; public final static String ID_REGEX = "^[0-9][0-9]\\" + ID_PREFIX_SEPARATOR + ".{12}" + ID_SEPARATOR + "[a-zA-Z0-9]{32}$"; + + public final static String DOI_REGEX = "(^10\\.[0-9]{4,9}\\/[-._;()\\/:a-zA-Z0-9]+$)|" + + "(^10\\.1002\\/[^\\s]+$)|" + + "(^10\\.1021\\/[a-zA-Z0-9_][a-zA-Z0-9_][0-9]++$)|" + + "(^10\\.1207\\/[a-zA-Z0-9_]+\\&[0-9]+_[0-9]+$)"; + public static final int ID_PREFIX_LEN = 12; public static final String NONE = "none"; @@ -48,10 +55,21 @@ public class IdentifierFactory implements Serializable { } protected static boolean pidFilter(StructuredProperty s) { - return Objects.nonNull(s.getQualifier()) && - PidType.isValid(s.getQualifier().getClassid()) && - StringUtils.isNotBlank(StringUtils.trim(s.getValue())) && - !NONE.equals(StringUtils.trim(StringUtils.lowerCase(s.getValue()))); + if (Objects.isNull(s.getQualifier()) || + StringUtils.isBlank(StringUtils.trim(s.getValue()))) { + return false; + } + try { + switch (PidType.valueOf(s.getQualifier().getClassid())) { + case doi: + final String doi = StringUtils.trim(StringUtils.lowerCase(s.getValue())); + return doi.matches(DOI_REGEX); + default: + return true; + } + } catch (IllegalArgumentException e) { + return false; + } } private static String verifyIdSyntax(String s) { From e5da4ee9b18842bc46a3a0a080fef943b051d3fa Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 4 Nov 2020 15:02:02 +0100 Subject: [PATCH 025/445] dedup workflow using the common PidComparator --- .../oaf/utils/OrganizationPidComparator.java | 27 +++ .../dhp/schema/oaf/utils/PidComparator.java | 64 +------ .../dnetlib/dhp/schema/oaf/utils/PidType.java | 13 +- .../schema/oaf/utils/ResultPidComparator.java | 57 +++++++ .../dhp/oa/dedup/DedupRecordFactory.java | 4 +- .../eu/dnetlib/dhp/oa/dedup/IdGenerator.java | 118 +++---------- .../dhp/oa/dedup/model/Identifier.java | 159 +++++++++--------- .../dnetlib/dhp/oa/dedup/model/PidType.java | 17 -- .../dhp/oa/dedup/EntityMergerTest.java | 106 ++++++------ .../dnetlib/dhp/oa/dedup/IdGeneratorTest.java | 111 ++++-------- .../dedup/json/publication_idgeneration.json | 6 +- .../dedup/json/publication_idgeneration2.json | 3 + .../dedup/json/publication_idgeneration3.json | 3 + .../dhp/dedup/json/publication_merge.json | 2 +- .../dhp/dedup/json/publication_merge2.json | 7 +- .../dhp/dedup/json/publication_merge3.json | 6 +- .../dhp/dedup/json/software_merge.json | 6 +- 17 files changed, 304 insertions(+), 405 deletions(-) create mode 100644 dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java create mode 100644 dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/ResultPidComparator.java delete mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/PidType.java create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_idgeneration2.json create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_idgeneration3.json diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java new file mode 100644 index 000000000..733d41bff --- /dev/null +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java @@ -0,0 +1,27 @@ + +package eu.dnetlib.dhp.schema.oaf.utils; + +import java.util.Comparator; + +public class OrganizationPidComparator implements Comparator { + + @Override + public int compare(PidType pLeft, PidType pRight) { + if (pLeft.equals(PidType.GRID)) + return -1; + if (pRight.equals(PidType.GRID)) + return 1; + + if (pLeft.equals(PidType.mag_id)) + return -1; + if (pRight.equals(PidType.mag_id)) + return 1; + + if (pLeft.equals(PidType.urn)) + return -1; + if (pRight.equals(PidType.urn)) + return 1; + + return 0; + } +} diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidComparator.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidComparator.java index d0a8f87ce..34ce5563f 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidComparator.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidComparator.java @@ -45,70 +45,10 @@ public class PidComparator implements Comparator { + + @Override + public int compare(PidType pLeft, PidType pRight) { + if (pLeft.equals(PidType.doi)) + return -1; + if (pRight.equals(PidType.doi)) + return 1; + + if (pLeft.equals(PidType.pmid)) + return -1; + if (pRight.equals(PidType.pmid)) + return 1; + + if (pLeft.equals(PidType.pmc)) + return -1; + if (pRight.equals(PidType.pmc)) + return 1; + + if (pLeft.equals(PidType.handle)) + return -1; + if (pRight.equals(PidType.handle)) + return 1; + + if (pLeft.equals(PidType.arXiv)) + return -1; + if (pRight.equals(PidType.arXiv)) + return 1; + + if (pLeft.equals(PidType.NCID)) + return -1; + if (pRight.equals(PidType.NCID)) + return 1; + + if (pLeft.equals(PidType.GBIF)) + return -1; + if (pRight.equals(PidType.GBIF)) + return 1; + + if (pLeft.equals(PidType.nct)) + return -1; + if (pRight.equals(PidType.nct)) + return 1; + + if (pLeft.equals(PidType.urn)) + return -1; + if (pRight.equals(PidType.urn)) + return 1; + + return 0; + } +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java index 025745f12..99cd7c31f 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java @@ -82,7 +82,7 @@ public class DedupRecordFactory { final Collection dates = Lists.newArrayList(); final List> authors = Lists.newArrayList(); - final List bestPids = Lists.newArrayList(); // best pids list + final List> bestPids = Lists.newArrayList(); // best pids list entities .forEachRemaining( @@ -90,7 +90,7 @@ public class DedupRecordFactory { T duplicate = t._2(); // prepare the list of pids to use for the id generation - bestPids.addAll(IdGenerator.bestPidToIdentifier(duplicate)); + bestPids.add(Identifier.newInstance(duplicate)); entity.mergeFrom(duplicate); if (ModelSupport.isSubClass(duplicate, Result.class)) { diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java index 405a9abf1..51e54ee4f 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java @@ -1,124 +1,46 @@ package eu.dnetlib.dhp.oa.dedup; +import static org.apache.commons.lang3.StringUtils.substringAfter; +import static org.apache.commons.lang3.StringUtils.substringBefore; + import java.io.Serializable; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.*; - -import org.apache.commons.lang.StringUtils; - -import com.google.common.collect.Lists; +import java.util.List; import eu.dnetlib.dhp.oa.dedup.model.Identifier; -import eu.dnetlib.dhp.oa.dedup.model.PidType; -import eu.dnetlib.dhp.schema.common.EntityType; -import eu.dnetlib.dhp.schema.common.ModelSupport; -import eu.dnetlib.dhp.schema.oaf.Field; import eu.dnetlib.dhp.schema.oaf.OafEntity; -import eu.dnetlib.dhp.schema.oaf.Result; -import eu.dnetlib.dhp.schema.oaf.StructuredProperty; -import eu.dnetlib.dhp.utils.DHPUtils; +import eu.dnetlib.dhp.schema.oaf.utils.PidType; public class IdGenerator implements Serializable { - public static String CROSSREF_ID = "10|openaire____::081b82f96300b6a6e3d282bad31cb6e2"; - public static String DATACITE_ID = "10|openaire____::9e3be59865b2c1c335d32dae2fe7b254"; - public static String BASE_DATE = "2000-01-01"; - // pick the best pid from the list (consider date and pidtype) - public static String generate(List pids, String defaultID) { + public static String generate(List> pids, String defaultID) { if (pids == null || pids.size() == 0) return defaultID; - Optional bp = pids + Identifier bp = pids .stream() - .max(Identifier::compareTo); + .min(Identifier::compareTo) + .get(); - if (bp.get().isUseOriginal() || bp.get().getPid().getValue() == null) { - return bp.get().getOriginalID().split("\\|")[0] + "|dedup_wf_001::" - + DHPUtils.md5(bp.get().getOriginalID()); + String prefix = substringBefore(bp.getOriginalID(), "|"); + String ns = substringBefore(substringAfter(bp.getOriginalID(), "|"), "::"); + String suffix = substringAfter(bp.getOriginalID(), "::"); + + final String pidType = substringBefore(ns, "_"); + if (PidType.isValid(pidType)) { + return prefix + "|" + dedupify(ns) + "::" + suffix; } else { - return bp.get().getOriginalID().split("\\|")[0] + "|" - + createPrefix(bp.get().getPid().getQualifier().getClassid()) + "::" - + DHPUtils.md5(bp.get().getPid().getValue()); + return prefix + "|dedup_wf_001::" + suffix; } - } - public static ArrayList createBasePid(T entity, SimpleDateFormat sdf) { - - Date date; - try { - date = sdf.parse(BASE_DATE); - } catch (ParseException e) { - date = new Date(); - } - return Lists - .newArrayList( - new Identifier(new StructuredProperty(), date, PidType.original, entity.getCollectedfrom(), - EntityType.fromClass(entity.getClass()), entity.getId())); - } - - // pick the best pid from the entity. Returns a list (length 1) to save time in the call - public static List bestPidToIdentifier(T entity) { - - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - - if (entity.getPid() == null || entity.getPid().size() == 0) - return createBasePid(entity, sdf); - - Optional bp = entity - .getPid() - .stream() - .filter(pid -> PidType.classidValueOf(pid.getQualifier().getClassid()) != PidType.undefined) - .max(Comparator.comparing(pid -> PidType.classidValueOf(pid.getQualifier().getClassid()))); - - return bp - .map( - structuredProperty -> Lists - .newArrayList( - new Identifier(structuredProperty, extractDate(entity, sdf), - PidType.classidValueOf(structuredProperty.getQualifier().getClassid()), - entity.getCollectedfrom(), EntityType.fromClass(entity.getClass()), entity.getId()))) - .orElseGet(() -> createBasePid(entity, sdf)); - - } - - // create the prefix (length = 12): dedup_+ pidType - public static String createPrefix(String pidType) { - - StringBuilder prefix = new StringBuilder("dedup_" + pidType); - + private static String dedupify(String ns) { + StringBuilder prefix = new StringBuilder(substringBefore(ns, "_")).append("_dedup"); while (prefix.length() < 12) { prefix.append("_"); } - return prefix.toString().substring(0, 12); - + return prefix.substring(0, 12); } - // extracts the date from the record. If the date is not available or is not wellformed, it returns a base date: - // 00-01-01 - public static Date extractDate(T duplicate, SimpleDateFormat sdf) { - - String date = BASE_DATE; - if (ModelSupport.isSubClass(duplicate, Result.class)) { - Result result = (Result) duplicate; - if (isWellformed(result.getDateofacceptance())) { - date = result.getDateofacceptance().getValue(); - } - } - - try { - return sdf.parse(date); - } catch (ParseException e) { - return new Date(); - } - - } - - public static boolean isWellformed(Field date) { - return date != null && StringUtils.isNotBlank(date.getValue()) - && date.getValue().matches(DatePicker.DATE_PATTERN) && DatePicker.inRange(date.getValue()); - } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java index dcbeb57f5..39b26f919 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java @@ -2,94 +2,85 @@ package eu.dnetlib.dhp.oa.dedup.model; import java.io.Serializable; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; + import com.google.common.collect.Sets; -import eu.dnetlib.dhp.oa.dedup.IdGenerator; +import eu.dnetlib.dhp.oa.dedup.DatePicker; import eu.dnetlib.dhp.schema.common.EntityType; -import eu.dnetlib.dhp.schema.oaf.KeyValue; -import eu.dnetlib.dhp.schema.oaf.StructuredProperty; +import eu.dnetlib.dhp.schema.common.ModelSupport; +import eu.dnetlib.dhp.schema.oaf.*; +import eu.dnetlib.dhp.schema.oaf.utils.PidComparator; +import eu.dnetlib.dhp.schema.oaf.utils.PidType; -public class Identifier implements Serializable, Comparable { +public class Identifier implements Serializable, Comparable { - StructuredProperty pid; - Date date; - PidType type; - List collectedFrom; - EntityType entityType; - String originalID; + public static String CROSSREF_ID = "10|openaire____::081b82f96300b6a6e3d282bad31cb6e2"; + public static String DATACITE_ID = "10|openaire____::9e3be59865b2c1c335d32dae2fe7b254"; + public static String BASE_DATE = "2000-01-01"; - boolean useOriginal = false; // to know if the top identifier won because of the alphabetical order of the original - // ID + private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - public Identifier(StructuredProperty pid, Date date, PidType type, List collectedFrom, - EntityType entityType, String originalID) { - this.pid = pid; - this.date = date; - this.type = type; - this.collectedFrom = collectedFrom; - this.entityType = entityType; - this.originalID = originalID; + private T entity; + + public static Identifier newInstance(T entity) { + return new Identifier(entity); } - public StructuredProperty getPid() { - return pid; + public Identifier(T entity) { + this.entity = entity; } - public void setPid(StructuredProperty pid) { - this.pid = pid; + public T getEntity() { + return entity; + } + + public void setEntity(T entity) { + this.entity = entity; } public Date getDate() { - return date; + String date = BASE_DATE; + if (ModelSupport.isSubClass(getEntity(), Result.class)) { + Result result = (Result) getEntity(); + if (isWellformed(result.getDateofacceptance())) { + date = result.getDateofacceptance().getValue(); + } + } + try { + return sdf.parse(date); + } catch (ParseException e) { + return new Date(); + } } - public void setDate(Date date) { - this.date = date; - } - - public PidType getType() { - return type; - } - - public void setType(PidType type) { - this.type = type; + private static boolean isWellformed(Field date) { + return date != null && StringUtils.isNotBlank(date.getValue()) + && date.getValue().matches(DatePicker.DATE_PATTERN) && DatePicker.inRange(date.getValue()); } public List getCollectedFrom() { - return collectedFrom; - } - - public void setCollectedFrom(List collectedFrom) { - this.collectedFrom = collectedFrom; + return entity.getCollectedfrom(); } public EntityType getEntityType() { - return entityType; - } - - public void setEntityType(EntityType entityType) { - this.entityType = entityType; + return EntityType.fromClass(entity.getClass()); } public String getOriginalID() { - return originalID; + return entity.getId(); } - public void setOriginalID(String originalID) { - this.originalID = originalID; - } - - public boolean isUseOriginal() { - return useOriginal; - } - - public void setUseOriginal(boolean useOriginal) { - this.useOriginal = useOriginal; + private PidType getPidType() { + return PidType.tryValueOf(StringUtils.substringBefore(StringUtils.substringAfter(entity.getId(), "|"), "_")); } @Override @@ -97,50 +88,50 @@ public class Identifier implements Serializable, Comparable { // priority in comparisons: 1) pidtype, 2) collectedfrom (depending on the entity type) , 3) date 4) // alphabetical order of the originalID - Set lKeys = Sets.newHashSet(); - if (this.collectedFrom != null) - lKeys = this.collectedFrom.stream().map(KeyValue::getKey).collect(Collectors.toSet()); + Set lKeys = Optional + .ofNullable(getCollectedFrom()) + .map(c -> c.stream().map(KeyValue::getKey).collect(Collectors.toSet())) + .orElse(Sets.newHashSet()); - Set rKeys = Sets.newHashSet(); - if (i.getCollectedFrom() != null) - rKeys = i.getCollectedFrom().stream().map(KeyValue::getKey).collect(Collectors.toSet()); + final Optional> cf = Optional.ofNullable(i.getCollectedFrom()); + Set rKeys = cf + .map(c -> c.stream().map(KeyValue::getKey).collect(Collectors.toSet())) + .orElse(Sets.newHashSet()); - if (this.getType().compareTo(i.getType()) == 0) { // same type - if (entityType == EntityType.publication) { - if (isFromDatasourceID(lKeys, IdGenerator.CROSSREF_ID) - && !isFromDatasourceID(rKeys, IdGenerator.CROSSREF_ID)) - return 1; - if (isFromDatasourceID(rKeys, IdGenerator.CROSSREF_ID) - && !isFromDatasourceID(lKeys, IdGenerator.CROSSREF_ID)) + if (this.getPidType().compareTo(i.getPidType()) == 0) { // same type + if (getEntityType() == EntityType.publication) { + if (isFromDatasourceID(lKeys, CROSSREF_ID) + && !isFromDatasourceID(rKeys, CROSSREF_ID)) return -1; + if (isFromDatasourceID(rKeys, CROSSREF_ID) + && !isFromDatasourceID(lKeys, CROSSREF_ID)) + return 1; } - if (entityType == EntityType.dataset) { - if (isFromDatasourceID(lKeys, IdGenerator.DATACITE_ID) - && !isFromDatasourceID(rKeys, IdGenerator.DATACITE_ID)) - return 1; - if (isFromDatasourceID(rKeys, IdGenerator.DATACITE_ID) - && !isFromDatasourceID(lKeys, IdGenerator.DATACITE_ID)) + if (getEntityType() == EntityType.dataset) { + if (isFromDatasourceID(lKeys, DATACITE_ID) + && !isFromDatasourceID(rKeys, DATACITE_ID)) return -1; + if (isFromDatasourceID(rKeys, DATACITE_ID) + && !isFromDatasourceID(lKeys, DATACITE_ID)) + return 1; } if (this.getDate().compareTo(i.getDate()) == 0) {// same date - - if (this.originalID.compareTo(i.originalID) < 0) - this.useOriginal = true; - else - i.setUseOriginal(true); - // the minus because we need to take the alphabetically lower id - return -this.originalID.compareTo(i.originalID); + return this.getOriginalID().compareTo(i.getOriginalID()); } else // the minus is because we need to take the elder date - return -this.getDate().compareTo(i.getDate()); + return this.getDate().compareTo(i.getDate()); } else { - return this.getType().compareTo(i.getType()); + return new PidComparator<>(getEntity()).compare(toSP(getPidType()), toSP(i.getPidType())); } } + private StructuredProperty toSP(PidType pidType) { + return OafMapperUtils.structuredProperty("", pidType.toString(), pidType.toString(), "", "", new DataInfo()); + } + public boolean isFromDatasourceID(Set collectedFrom, String dsId) { return collectedFrom.contains(dsId); } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/PidType.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/PidType.java deleted file mode 100644 index cb9b2bd15..000000000 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/PidType.java +++ /dev/null @@ -1,17 +0,0 @@ - -package eu.dnetlib.dhp.oa.dedup.model; - -public enum PidType { - - // from the less to the more important - undefined, original, orcid, ror, grid, pdb, arXiv, pmid, pmc, doi; - - public static PidType classidValueOf(String s) { - try { - return PidType.valueOf(s); - } catch (Exception e) { - return PidType.undefined; - } - } - -} diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java index bb4188f7e..3f10af5b8 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.io.Serializable; import java.nio.file.Paths; import java.util.*; +import java.util.stream.Collectors; import org.codehaus.jackson.map.ObjectMapper; import org.junit.jupiter.api.BeforeEach; @@ -21,16 +22,16 @@ import scala.Tuple2; public class EntityMergerTest implements Serializable { - List> publications; - List> publications2; - List> publications3; - List> publications4; - List> publications5; + private List> publications; + private List> publications2; + private List> publications3; + private List> publications4; + private List> publications5; - String testEntityBasePath; - DataInfo dataInfo; - String dedupId = "00|dedup_id::1"; - Publication pub_top; + private String testEntityBasePath; + private DataInfo dataInfo; + private String dedupId = "00|dedup_id::1"; + private Publication pub_top; @BeforeEach public void setUp() throws Exception { @@ -61,9 +62,9 @@ public class EntityMergerTest implements Serializable { Software merged = DedupRecordFactory .entityMerger(dedupId, softwares.iterator(), 0, dataInfo, Software.class); - assertEquals(merged.getBestaccessright().getClassid(), "OPEN SOURCE"); + assertEquals("OPEN SOURCE", merged.getBestaccessright().getClassid()); - assertEquals(merged.getId(), "50|dedup_doi___::0968af610a356656706657e4f234b340"); + assertEquals("50|doi_dedup___::0968af610a356656706657e4f234b340", merged.getId()); } @@ -74,45 +75,45 @@ public class EntityMergerTest implements Serializable { .entityMerger(dedupId, publications.iterator(), 0, dataInfo, Publication.class); // verify id - assertEquals(pub_merged.getId(), "50|dedup_doi___::0968af610a356656706657e4f234b340"); + assertEquals("50|doi_dedup___::0968af610a356656706657e4f234b340", pub_merged.getId()); - assertEquals(pub_merged.getJournal(), pub_top.getJournal()); - assertEquals(pub_merged.getBestaccessright().getClassid(), "OPEN"); - assertEquals(pub_merged.getResulttype(), pub_top.getResulttype()); - assertEquals(pub_merged.getLanguage(), pub_merged.getLanguage()); - assertEquals(pub_merged.getPublisher(), pub_top.getPublisher()); - assertEquals(pub_merged.getEmbargoenddate(), pub_top.getEmbargoenddate()); - assertEquals(pub_merged.getResourcetype().getClassid(), "0004"); - assertEquals(pub_merged.getDateoftransformation(), pub_top.getDateoftransformation()); - assertEquals(pub_merged.getOaiprovenance(), pub_top.getOaiprovenance()); - assertEquals(pub_merged.getDateofcollection(), pub_top.getDateofcollection()); - assertEquals(pub_merged.getInstance().size(), 3); - assertEquals(pub_merged.getCountry().size(), 2); - assertEquals(pub_merged.getSubject().size(), 0); - assertEquals(pub_merged.getTitle().size(), 2); - assertEquals(pub_merged.getRelevantdate().size(), 0); - assertEquals(pub_merged.getDescription().size(), 0); - assertEquals(pub_merged.getSource().size(), 0); - assertEquals(pub_merged.getFulltext().size(), 0); - assertEquals(pub_merged.getFormat().size(), 0); - assertEquals(pub_merged.getContributor().size(), 0); - assertEquals(pub_merged.getCoverage().size(), 0); - assertEquals(pub_merged.getContext().size(), 0); - assertEquals(pub_merged.getExternalReference().size(), 0); - assertEquals(pub_merged.getOriginalId().size(), 3); - assertEquals(pub_merged.getCollectedfrom().size(), 3); - assertEquals(pub_merged.getPid().size(), 1); - assertEquals(pub_merged.getExtraInfo().size(), 0); + assertEquals(pub_top.getJournal(), pub_merged.getJournal()); + assertEquals("OPEN", pub_merged.getBestaccessright().getClassid()); + assertEquals(pub_top.getResulttype(), pub_merged.getResulttype()); + assertEquals(pub_top.getLanguage(), pub_merged.getLanguage()); + assertEquals(pub_top.getPublisher(), pub_merged.getPublisher()); + assertEquals(pub_top.getEmbargoenddate(), pub_merged.getEmbargoenddate()); + assertEquals(pub_top.getResourcetype().getClassid(), ""); + assertEquals(pub_top.getDateoftransformation(), pub_merged.getDateoftransformation()); + assertEquals(pub_top.getOaiprovenance(), pub_merged.getOaiprovenance()); + assertEquals(pub_top.getDateofcollection(), pub_merged.getDateofcollection()); + assertEquals(3, pub_merged.getInstance().size()); + assertEquals(2, pub_merged.getCountry().size()); + assertEquals(0, pub_merged.getSubject().size()); + assertEquals(2, pub_merged.getTitle().size()); + assertEquals(0, pub_merged.getRelevantdate().size()); + assertEquals(0, pub_merged.getDescription().size()); + assertEquals(0, pub_merged.getSource().size()); + assertEquals(0, pub_merged.getFulltext().size()); + assertEquals(0, pub_merged.getFormat().size()); + assertEquals(0, pub_merged.getContributor().size()); + assertEquals(0, pub_merged.getCoverage().size()); + assertEquals(0, pub_merged.getContext().size()); + assertEquals(0, pub_merged.getExternalReference().size()); + assertEquals(3, pub_merged.getOriginalId().size()); + assertEquals(3, pub_merged.getCollectedfrom().size()); + assertEquals(1, pub_merged.getPid().size()); + assertEquals(0, pub_merged.getExtraInfo().size()); // verify datainfo - assertEquals(pub_merged.getDataInfo(), dataInfo); + assertEquals(dataInfo, pub_merged.getDataInfo()); // verify datepicker - assertEquals(pub_merged.getDateofacceptance().getValue(), "2018-09-30"); + assertEquals("2018-09-30", pub_merged.getDateofacceptance().getValue()); // verify authors - assertEquals(pub_merged.getAuthor().size(), 9); - assertEquals(AuthorMerger.countAuthorsPids(pub_merged.getAuthor()), 4); + assertEquals(9, pub_merged.getAuthor().size()); + assertEquals(4, AuthorMerger.countAuthorsPids(pub_merged.getAuthor())); // verify title int count = 0; @@ -120,7 +121,7 @@ public class EntityMergerTest implements Serializable { if (title.getQualifier().getClassid().equals("main title")) count++; } - assertEquals(count, 1); + assertEquals(1, count); } @Test @@ -130,9 +131,9 @@ public class EntityMergerTest implements Serializable { .entityMerger(dedupId, publications2.iterator(), 0, dataInfo, Publication.class); // verify id - assertEquals("50|dedup_doi___::0ca46ff10b2b4c756191719d85302b14", pub_merged.getId()); + assertEquals("50|doi_dedup___::0ca46ff10b2b4c756191719d85302b14", pub_merged.getId()); - assertEquals(pub_merged.getAuthor().size(), 27); + assertEquals(27, pub_merged.getAuthor().size()); } @Test @@ -142,7 +143,7 @@ public class EntityMergerTest implements Serializable { .entityMerger(dedupId, publications3.iterator(), 0, dataInfo, Publication.class); // verify id - assertEquals("50|dedup_doi___::0ca46ff10b2b4c756191719d85302b14", pub_merged.getId()); + assertEquals("50|doi_dedup___::0ca46ff10b2b4c756191719d85302b14", pub_merged.getId()); } @Test @@ -152,17 +153,24 @@ public class EntityMergerTest implements Serializable { .entityMerger(dedupId, publications4.iterator(), 0, dataInfo, Publication.class); // verify id - assertEquals("50|dedup_wf_001::2d2bbbbcfb285e3fb3590237b79e2fa8", pub_merged.getId()); + assertEquals("50|dedup_wf_001::0ca46ff10b2b4c756191719d85302b14", pub_merged.getId()); } @Test public void publicationMergerTest5() throws InstantiationException, IllegalStateException, IllegalAccessException { + System.out + .println( + publications5 + .stream() + .map(p -> p._2().getId()) + .collect(Collectors.toList())); + Publication pub_merged = DedupRecordFactory .entityMerger(dedupId, publications5.iterator(), 0, dataInfo, Publication.class); // verify id - assertEquals("50|dedup_wf_001::584b89679c3ccd1015b647ec63cc2699", pub_merged.getId()); + assertEquals("50|dedup_wf_001::0ca46ff10b2b4c756191719d85302b14", pub_merged.getId()); } public DataInfo setDI() { diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java index 6089babf7..a6604dd30 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java @@ -7,97 +7,57 @@ import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.nio.file.Paths; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Date; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; -import org.codehaus.jackson.map.ObjectMapper; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; import eu.dnetlib.dhp.oa.dedup.model.Identifier; -import eu.dnetlib.dhp.oa.dedup.model.PidType; -import eu.dnetlib.dhp.schema.common.EntityType; -import eu.dnetlib.dhp.schema.oaf.KeyValue; -import eu.dnetlib.dhp.schema.oaf.Publication; -import eu.dnetlib.dhp.schema.oaf.Qualifier; -import eu.dnetlib.dhp.schema.oaf.StructuredProperty; +import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.pace.util.MapDocumentUtil; import scala.Tuple2; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class IdGeneratorTest { - private static List bestIds; - private static List> pubs; + private static ObjectMapper OBJECT_MAPPER = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - private static List bestIds2; - private static List bestIds3; + private static List> bestIds; + private static List> bestIds2; + private static List> bestIds3; private static String testEntityBasePath; - private static SimpleDateFormat sdf; - private static Date baseDate; - @BeforeAll public static void setUp() throws Exception { - - sdf = new SimpleDateFormat("yyyy-MM-dd"); - baseDate = sdf.parse("2000-01-01"); - - bestIds = new ArrayList<>(); - bestIds2 = Lists - .newArrayList( - new Identifier(pid("pid1", "original", "original"), baseDate, PidType.original, - keyValue("key", "value"), EntityType.publication, "50|originalID1"), - new Identifier(pid("pid2", "original", "original"), baseDate, PidType.original, - keyValue("key", "value"), EntityType.publication, "50|originalID2"), - new Identifier(pid("pid3", "original", "original"), baseDate, PidType.original, - keyValue("key", "value"), EntityType.publication, "50|originalID3")); - bestIds3 = Lists - .newArrayList( - new Identifier(pid("pid1", "original", "original"), baseDate, PidType.original, - keyValue("key", "value"), EntityType.publication, "50|originalID1"), - new Identifier(pid("pid2", "doi", "doi"), baseDate, PidType.doi, keyValue("key", "value"), - EntityType.publication, "50|originalID2"), - new Identifier(pid("pid3", "original", "original"), baseDate, PidType.original, - keyValue("key", "value"), EntityType.publication, "50|originalID3")); - testEntityBasePath = Paths .get(SparkDedupTest.class.getResource("/eu/dnetlib/dhp/dedup/json").toURI()) .toFile() .getAbsolutePath(); - pubs = readSample(testEntityBasePath + "/publication_idgeneration.json", Publication.class); - + bestIds = createBestIds(testEntityBasePath + "/publication_idgeneration.json", Publication.class); + bestIds2 = createBestIds(testEntityBasePath + "/publication_idgeneration2.json", Publication.class); + bestIds3 = createBestIds(testEntityBasePath + "/publication_idgeneration3.json", Publication.class); } @Test - @Order(1) - public void bestPidToIdentifierTest() { - - List typesForAssertions = Lists - .newArrayList(PidType.pmc.toString(), PidType.doi.toString(), PidType.doi.toString()); - - for (Tuple2 pub : pubs) { - List ids = IdGenerator.bestPidToIdentifier(pub._2()); - assertEquals(typesForAssertions.get(pubs.indexOf(pub)), ids.get(0).getPid().getQualifier().getClassid()); - bestIds.addAll(ids); - } - } - - @Test - @Order(2) public void generateIdTest1() { String id1 = IdGenerator.generate(bestIds, "50|defaultID"); System.out - .println("id list 1 = " + bestIds.stream().map(i -> i.getPid().getValue()).collect(Collectors.toList())); + .println("id list 1 = " + bestIds.stream().map(i -> i.getOriginalID()).collect(Collectors.toList())); - assertEquals("50|dedup_wf_001::9c5cfbf993d38476e0f959a301239719", id1); + assertEquals("50|doi_dedup___::0968af610a356656706657e4f234b340", id1); } @Test @@ -106,14 +66,22 @@ public class IdGeneratorTest { String id2 = IdGenerator.generate(bestIds3, "50|defaultID"); System.out - .println("id list 2 = " + bestIds2.stream().map(i -> i.getPid().getValue()).collect(Collectors.toList())); + .println("id list 2 = " + bestIds2.stream().map(i -> i.getOriginalID()).collect(Collectors.toList())); System.out.println("winner 2 = " + id1); System.out - .println("id list 3 = " + bestIds3.stream().map(i -> i.getPid().getValue()).collect(Collectors.toList())); + .println("id list 3 = " + bestIds3.stream().map(i -> i.getOriginalID()).collect(Collectors.toList())); System.out.println("winner 3 = " + id2); - assertEquals("50|dedup_wf_001::2c56cc1914bffdb30fdff354e0099612", id1); - assertEquals("50|dedup_doi___::128ead3ed8d9ecf262704b6fcf592b8d", id2); + assertEquals("50|doi_dedup___::1a77a3bba737f8b669dcf330ad3b37e2", id1); + assertEquals("50|dedup_wf_001::0829b5191605bdbea36d6502b8c1ce1g", id2); + } + + protected static List> createBestIds(String path, Class clazz) { + final Stream> ids = readSample(path, clazz) + .stream() + .map(Tuple2::_2) + .map(Identifier::newInstance); + return ids.collect(Collectors.toList()); } public static List> readSample(String path, Class clazz) { @@ -127,7 +95,7 @@ public class IdGeneratorTest { .add( new Tuple2<>( MapDocumentUtil.getJPathString("$.id", line), - new ObjectMapper().readValue(line, clazz))); + OBJECT_MAPPER.readValue(line, clazz))); // read next line line = reader.readLine(); } @@ -140,23 +108,10 @@ public class IdGeneratorTest { } public static StructuredProperty pid(String pid, String classid, String classname) { - - StructuredProperty sp = new StructuredProperty(); - sp.setValue(pid); - Qualifier q = new Qualifier(); - q.setSchemeid(classid); - q.setSchemename(classname); - q.setClassname(classname); - q.setClassid(classid); - sp.setQualifier(q); - return sp; + return OafMapperUtils.structuredProperty(pid, classid, classname, "", "", new DataInfo()); } public static List keyValue(String key, String value) { - - KeyValue kv = new KeyValue(); - kv.setKey(key); - kv.setValue(value); - return Lists.newArrayList(kv); + return Lists.newArrayList(OafMapperUtils.keyValue(key, value)); } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_idgeneration.json b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_idgeneration.json index b2300acc6..4fc078204 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_idgeneration.json +++ b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_idgeneration.json @@ -1,3 +1,3 @@ -{"context": [], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:datasetarchive", "classname": "sysimport:crosswalk:datasetarchive", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "dedup-similarity-result-levenstein", "invisible": false, "trust": "0.95"}, "resourcetype": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "pid": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "arXiv", "classname": "arXiv", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "arXivSampleID"} ,{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "pmc", "classname": "pmc", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "pmcsampleid"}], "contributor": [], "resulttype": {"classid": "publication", "classname": "publication", "schemename": "dnet:result_typologies", "schemeid": "dnet:result_typologies"}, "relevantdate": [], "collectedfrom": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Journal.fi", "key": "10|openaire____::6eef8049d0feedc089ee009abca55e35"}], "id": "50|a89337edbe55::4930db9e954866d70916cbfba9f81f97", "subject": [], "instance": [{"refereed": null, "hostedby": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Journal.fi", "key": "10|openaire____::6eef8049d0feedc089ee009abca55e35"}, "processingchargeamount": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "license": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "url": [], "distributionlocation": "", "processingchargecurrency": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "collectedfrom": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Journal.fi", "key": "10|openaire____::6eef8049d0feedc089ee009abca55e35"}, "accessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "instancetype": {"classid": "0001", "classname": "Article", "schemename": "dnet:dataCite_resource", "schemeid": "dnet:dataCite_resource"}}], "embargoenddate": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "lastupdatetimestamp": 0, "author": [{"surname": "Go\\u0308tz", "name": "Julia", "pid": [], "rank": 5, "affiliation": [], "fullname": "G\\u00f6tz, Julia"}, {"surname": "Wolff", "name": "Stephan", "pid": [], "rank": 6, "affiliation": [], "fullname": "Wolff, Stephan"}, {"surname": "Jansen", "name": "Olav", "pid": [], "rank": 7, "affiliation": [], "fullname": "Jansen, Olav"}, {"surname": "Dressler", "name": "Dirk", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0000-0656-9999"},{"qualifier": {"classid": "id", "classname": "id"}, "value": "987654321"}], "rank": 8, "affiliation": [], "fullname": "Dressler, Dirk"}, {"surname": "Schneider", "name": "Susanne A.", "pid": [], "rank": 9, "affiliation": [], "fullname": "Schneider, Susanne A."}], "source": [], "dateofcollection": "2019-11-05T14:49:22.351Z", "fulltext": [], "dateoftransformation": "2019-11-05T16:10:58.988Z", "description": [], "format": [], "journal": {"issnPrinted": "1459-6067", "conferencedate": "", "conferenceplace": "", "name": "Agricultural and Food Science", "edition": "", "iss": "3", "sp": "", "vol": "27", "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "issnOnline": "1795-1895", "ep": "", "issnLinking": ""}, "coverage": [], "publisher": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "language": {"classid": "eng", "classname": "English", "schemename": "dnet:languages", "schemeid": "dnet:languages"}, "bestaccessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "country": [], "extraInfo": [], "originalId": [], "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2018-09-30"}, "title": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "main title", "classname": "main title", "schemename": "dnet:dataCite_title", "schemeid": "dnet:dataCite_title"}, "value": "Altered brain activation in a reversal learning task unmasks adaptive changes in cognitive control in writer's cramp"}]} -{"context": [], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:repository", "classname": "sysimport:crosswalk:repository", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": true, "inferenceprovenance": "dedup-similarity-result-levenstein", "invisible": false, "trust": "0.9"}, "resourcetype": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "pid": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "doi", "classname": "doi", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "10.1016/j.nicl.2015.11.006"}, {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "arXiv", "classname": "arXiv", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "arXivSampleID"} ,{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "pmc", "classname": "pmc", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "pmcsampleid"}], "contributor": [], "resulttype": {"classid": "publication", "classname": "publication", "schemename": "dnet:result_typologies", "schemeid": "dnet:result_typologies"}, "relevantdate": [], "collectedfrom": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "BASE (Open Access Aggregator)", "key": "10|openaire____::df45502607927471ecf8a6ae83683ff5"}], "id": "50|base_oa_____::0968af610a356656706657e4f234b340", "subject": [], "instance": [{"refereed": null, "hostedby": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "NeuroImage: Clinical", "key": "10|doajarticles::0c0e74daa5d95504eade9c81ebbd5b8a"}, "processingchargeamount": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "license": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "http://creativecommons.org/licenses/by-nc-nd/4.0/"}, "url": ["http://dx.doi.org/10.1016/j.nicl.2015.11.006"], "distributionlocation": "", "processingchargecurrency": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "collectedfrom": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "BASE (Open Access Aggregator)", "key": "10|openaire____::df45502607927471ecf8a6ae83683ff5"}, "accessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "instancetype": {"classid": "0001", "classname": "Article", "schemename": "dnet:publication_resource", "schemeid": "dnet:publication_resource"}}], "embargoenddate": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "lastupdatetimestamp": 0, "author": [{"surname": "Zeuner", "name": "Kirsten E.", "pid": [], "rank": 1, "affiliation": [], "fullname": "Zeuner, Kirsten E."}, {"surname": "Knutzen", "name": "Arne", "pid": [], "rank": 2, "affiliation": [], "fullname": "Knutzen, Arne"}, {"surname": "Granert", "name": "Oliver", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0002-0656-1023"}], "rank": 3, "affiliation": [], "fullname": "Granert, Oliver"}, {"surname": "Sablowsky", "name": "Simone", "pid": [], "rank": 4, "affiliation": [], "fullname": "Sablowsky, Simone"}, {"surname": "Go\\u0308tz", "name": "Julia", "pid": [], "rank": 5, "affiliation": [], "fullname": "G\\u00f6tz, Julia"}, {"surname": "Wolff", "name": "Stephan", "pid": [], "rank": 6, "affiliation": [], "fullname": "Wolff, Stephan"}, {"surname": "Jansen", "name": "Olav", "pid": [], "rank": 7, "affiliation": [], "fullname": "Jansen, Olav"}, {"surname": "Dressler", "name": "Dirk", "pid": [], "rank": 8, "affiliation": [], "fullname": "Dressler, Dirk"}, {"surname": "Schneider", "name": "Susanne A.", "pid": [], "rank": 9, "affiliation": [], "fullname": "Schneider, Susanne A."}, {"surname": "Klein", "name": "Christine", "pid": [], "rank": 10, "affiliation": [], "fullname": "Klein, Christine"}, {"surname": "Deuschl", "name": "Gu\\u0308nther", "pid": [], "rank": 11, "affiliation": [], "fullname": "Deuschl, G\\u00fcnther"}, {"surname": "Eimeren", "name": "Thilo", "pid": [], "rank": 12, "affiliation": [], "fullname": "van Eimeren, Thilo"}, {"surname": "Witt", "name": "Karsten", "pid": [], "rank": 13, "affiliation": [], "fullname": "Witt, Karsten"}], "source": [], "dateofcollection": "2017-07-27T19:04:09.131Z", "fulltext": [], "dateoftransformation": "2019-01-23T10:15:19.582Z", "description": [], "format": [], "journal": {"issnPrinted": "2213-1582", "conferencedate": "", "conferenceplace": "", "name": "NeuroImage: Clinical", "edition": "", "iss": "", "sp": "63", "vol": "10", "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "issnOnline": "", "ep": "70", "issnLinking": ""}, "coverage": [], "publisher": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Elsevier BV"}, "language": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "bestaccessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "country": [{"classid": "IT", "classname": "Italy", "schemeid": "dnet:countries", "schemename": "dnet:countries"}], "extraInfo": [], "originalId": ["10.1016/j.nicl.2015.11.006"], "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "title": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "main title", "classname": "main title", "schemename": "dnet:dataCite_title", "schemeid": "dnet:dataCite_title"}, "value": "Altered brain activation in a reversal learning task unmasks adaptive changes in cognitive control in writer's cramp"}]} -{"context": [], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:datasetarchive", "classname": "sysimport:crosswalk:datasetarchive", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": true, "inferenceprovenance": "dedup-similarity-result-levenstein", "invisible": false, "trust": "0.9"}, "resourcetype": {"classid": "0004", "classname": "Conference object", "schemename": "dnet:dataCite_resource", "schemeid": "dnet:dataCite_resource"}, "pid": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "arXiv", "classname": "arXiv", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "arXivSampleID"} ,{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "doi", "classname": "doi", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "10.0001/doi2"}], "contributor": [], "resulttype": {"classid": "publication", "classname": "publication", "schemename": "dnet:result_typologies", "schemeid": "dnet:result_typologies"}, "relevantdate": [], "collectedfrom": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "CRIS UNS (Current Research Information System University of Novi Sad)", "key": "10|CRIS_UNS____::f66f1bd369679b5b077dcdf006089556"}], "id": "50|CrisUnsNoviS::9f9d014eea45dab432cab636c4c9cf39", "subject": [], "instance": [{"refereed": null, "hostedby": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "CRIS UNS (Current Research Information System University of Novi Sad)", "key": "10|CRIS_UNS____::f66f1bd369679b5b077dcdf006089556"}, "processingchargeamount": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "license": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "url": ["https://www.cris.uns.ac.rs/record.jsf?recordId=113444&source=OpenAIRE&language=en"], "distributionlocation": "", "processingchargecurrency": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2019-01-01"}, "collectedfrom": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "CRIS UNS (Current Research Information System University of Novi Sad)", "key": "10|CRIS_UNS____::f66f1bd369679b5b077dcdf006089556"}, "accessright": {"classid": "UNKNOWN", "classname": "UNKNOWN", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "instancetype": {"classid": "0004", "classname": "Conference object", "schemename": "dnet:dataCite_resource", "schemeid": "dnet:dataCite_resource"}}], "embargoenddate": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "lastupdatetimestamp": 0, "author": [{"surname": "Zeuner", "name": "Kirsten E.", "pid": [], "rank": 1, "affiliation": [], "fullname": "Zeuner, Kirsten E."}, {"surname": "Knutzen", "name": "Arne", "pid": [], "rank": 2, "affiliation": [], "fullname": "Knutzen, Arne"}, {"surname": "Granert", "name": "Oliver", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0002-0656-1023"}, {"qualifier": {"classid": "pubmed", "classname": "pubmed"}, "value": "pubmed.it"}], "rank": 3, "affiliation": [], "fullname": "Granert, Oliver"}, {"surname": "Sablowsky", "name": "Simone", "pid": [{"qualifier": {"classid": "id", "classname": "id"}, "value": "12345678"}], "rank": 4, "affiliation": [], "fullname": "Sablowsky, Simone"}, {"surname": "Go\\u0308tz", "name": "Julia", "pid": [], "rank": 5, "affiliation": [], "fullname": "G\\u00f6tz, Julia"}, {"surname": "Wolff", "name": "Stephan", "pid": [], "rank": 6, "affiliation": [], "fullname": "Wolff, Stephan"}, {"surname": "Jansen", "name": "Olav", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0000-0656-1023"},{"qualifier": {"classid": "id", "classname": "id"}, "value": "987654321"}], "rank": 7, "affiliation": [], "fullname": "Jansen, Olav"}, {"surname": "Dressler", "name": "Dirk", "pid": [], "rank": 8, "affiliation": [], "fullname": "Dressler, Dirk"}, {"surname": "Schneider", "name": "Susanne A.", "pid": [], "rank": 9, "affiliation": [], "fullname": "Schneider, Susanne A."}], "source": [], "dateofcollection": "2020-03-10T15:05:38.685Z", "fulltext": [], "dateoftransformation": "2020-03-11T20:11:13.15Z", "description": [], "format": [], "journal": {"issnPrinted": "", "conferencedate": "", "conferenceplace": "", "name": "", "edition": "", "iss": "", "sp": "", "vol": "", "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "issnOnline": "", "ep": "", "issnLinking": ""}, "coverage": [], "publisher": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "language": {"classid": "en", "classname": "en", "schemename": "dnet:languages", "schemeid": "dnet:languages"}, "bestaccessright": {"classid": "UNKNOWN", "classname": "not available", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "country": [{"classid": "FI", "classname": "Finland", "schemeid": "dnet:countries", "schemename": "dnet:countries"}], "extraInfo": [], "originalId": ["(BISIS)113444", "https://www.cris.uns.ac.rs/record.jsf?recordId=113444&source=OpenAIRE&language=en"], "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "title": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "test title", "classname": "test title", "schemename": "dnet:dataCite_title", "schemeid": "dnet:dataCite_title"}, "value": "Antichains of copies of ultrahomogeneous structures"}]} \ No newline at end of file +{"context": [], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:datasetarchive", "classname": "sysimport:crosswalk:datasetarchive", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "dedup-similarity-result-levenstein", "invisible": false, "trust": "0.95"}, "resourcetype": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "pid": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "arXiv", "classname": "arXiv", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "arXivSampleID"} ,{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "pmc", "classname": "pmc", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "pmcsampleid"}], "contributor": [], "resulttype": {"classid": "publication", "classname": "publication", "schemename": "dnet:result_typologies", "schemeid": "dnet:result_typologies"}, "relevantdate": [], "collectedfrom": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Journal.fi", "key": "10|openaire____::6eef8049d0feedc089ee009abca55e35"}], "id": "50|pmc_________::4930db9e954866d70916cbfba9f81f97", "subject": [], "instance": [{"refereed": null, "hostedby": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Journal.fi", "key": "10|openaire____::6eef8049d0feedc089ee009abca55e35"}, "processingchargeamount": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "license": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "url": [], "distributionlocation": "", "processingchargecurrency": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "collectedfrom": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Journal.fi", "key": "10|openaire____::6eef8049d0feedc089ee009abca55e35"}, "accessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "instancetype": {"classid": "0001", "classname": "Article", "schemename": "dnet:dataCite_resource", "schemeid": "dnet:dataCite_resource"}}], "embargoenddate": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "lastupdatetimestamp": 0, "author": [{"surname": "Go\\u0308tz", "name": "Julia", "pid": [], "rank": 5, "affiliation": [], "fullname": "G\\u00f6tz, Julia"}, {"surname": "Wolff", "name": "Stephan", "pid": [], "rank": 6, "affiliation": [], "fullname": "Wolff, Stephan"}, {"surname": "Jansen", "name": "Olav", "pid": [], "rank": 7, "affiliation": [], "fullname": "Jansen, Olav"}, {"surname": "Dressler", "name": "Dirk", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0000-0656-9999"},{"qualifier": {"classid": "id", "classname": "id"}, "value": "987654321"}], "rank": 8, "affiliation": [], "fullname": "Dressler, Dirk"}, {"surname": "Schneider", "name": "Susanne A.", "pid": [], "rank": 9, "affiliation": [], "fullname": "Schneider, Susanne A."}], "source": [], "dateofcollection": "2019-11-05T14:49:22.351Z", "fulltext": [], "dateoftransformation": "2019-11-05T16:10:58.988Z", "description": [], "format": [], "journal": {"issnPrinted": "1459-6067", "conferencedate": "", "conferenceplace": "", "name": "Agricultural and Food Science", "edition": "", "iss": "3", "sp": "", "vol": "27", "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "issnOnline": "1795-1895", "ep": "", "issnLinking": ""}, "coverage": [], "publisher": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "language": {"classid": "eng", "classname": "English", "schemename": "dnet:languages", "schemeid": "dnet:languages"}, "bestaccessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "country": [], "extraInfo": [], "originalId": [], "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2018-09-30"}, "title": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "main title", "classname": "main title", "schemename": "dnet:dataCite_title", "schemeid": "dnet:dataCite_title"}, "value": "Altered brain activation in a reversal learning task unmasks adaptive changes in cognitive control in writer's cramp"}]} +{"context": [], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:repository", "classname": "sysimport:crosswalk:repository", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": true, "inferenceprovenance": "dedup-similarity-result-levenstein", "invisible": false, "trust": "0.9"}, "resourcetype": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "pid": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "doi", "classname": "doi", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "10.1016/j.nicl.2015.11.006"}, {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "arXiv", "classname": "arXiv", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "arXivSampleID"} ,{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "pmc", "classname": "pmc", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "pmcsampleid"}], "contributor": [], "resulttype": {"classid": "publication", "classname": "publication", "schemename": "dnet:result_typologies", "schemeid": "dnet:result_typologies"}, "relevantdate": [], "collectedfrom": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "BASE (Open Access Aggregator)", "key": "10|openaire____::df45502607927471ecf8a6ae83683ff5"}], "id": "50|doi_________::0968af610a356656706657e4f234b340", "subject": [], "instance": [{"refereed": null, "hostedby": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "NeuroImage: Clinical", "key": "10|doajarticles::0c0e74daa5d95504eade9c81ebbd5b8a"}, "processingchargeamount": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "license": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "http://creativecommons.org/licenses/by-nc-nd/4.0/"}, "url": ["http://dx.doi.org/10.1016/j.nicl.2015.11.006"], "distributionlocation": "", "processingchargecurrency": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "collectedfrom": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "BASE (Open Access Aggregator)", "key": "10|openaire____::df45502607927471ecf8a6ae83683ff5"}, "accessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "instancetype": {"classid": "0001", "classname": "Article", "schemename": "dnet:publication_resource", "schemeid": "dnet:publication_resource"}}], "embargoenddate": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "lastupdatetimestamp": 0, "author": [{"surname": "Zeuner", "name": "Kirsten E.", "pid": [], "rank": 1, "affiliation": [], "fullname": "Zeuner, Kirsten E."}, {"surname": "Knutzen", "name": "Arne", "pid": [], "rank": 2, "affiliation": [], "fullname": "Knutzen, Arne"}, {"surname": "Granert", "name": "Oliver", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0002-0656-1023"}], "rank": 3, "affiliation": [], "fullname": "Granert, Oliver"}, {"surname": "Sablowsky", "name": "Simone", "pid": [], "rank": 4, "affiliation": [], "fullname": "Sablowsky, Simone"}, {"surname": "Go\\u0308tz", "name": "Julia", "pid": [], "rank": 5, "affiliation": [], "fullname": "G\\u00f6tz, Julia"}, {"surname": "Wolff", "name": "Stephan", "pid": [], "rank": 6, "affiliation": [], "fullname": "Wolff, Stephan"}, {"surname": "Jansen", "name": "Olav", "pid": [], "rank": 7, "affiliation": [], "fullname": "Jansen, Olav"}, {"surname": "Dressler", "name": "Dirk", "pid": [], "rank": 8, "affiliation": [], "fullname": "Dressler, Dirk"}, {"surname": "Schneider", "name": "Susanne A.", "pid": [], "rank": 9, "affiliation": [], "fullname": "Schneider, Susanne A."}, {"surname": "Klein", "name": "Christine", "pid": [], "rank": 10, "affiliation": [], "fullname": "Klein, Christine"}, {"surname": "Deuschl", "name": "Gu\\u0308nther", "pid": [], "rank": 11, "affiliation": [], "fullname": "Deuschl, G\\u00fcnther"}, {"surname": "Eimeren", "name": "Thilo", "pid": [], "rank": 12, "affiliation": [], "fullname": "van Eimeren, Thilo"}, {"surname": "Witt", "name": "Karsten", "pid": [], "rank": 13, "affiliation": [], "fullname": "Witt, Karsten"}], "source": [], "dateofcollection": "2017-07-27T19:04:09.131Z", "fulltext": [], "dateoftransformation": "2019-01-23T10:15:19.582Z", "description": [], "format": [], "journal": {"issnPrinted": "2213-1582", "conferencedate": "", "conferenceplace": "", "name": "NeuroImage: Clinical", "edition": "", "iss": "", "sp": "63", "vol": "10", "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "issnOnline": "", "ep": "70", "issnLinking": ""}, "coverage": [], "publisher": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Elsevier BV"}, "language": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "bestaccessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "country": [{"classid": "IT", "classname": "Italy", "schemeid": "dnet:countries", "schemename": "dnet:countries"}], "extraInfo": [], "originalId": ["10.1016/j.nicl.2015.11.006"], "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "title": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "main title", "classname": "main title", "schemename": "dnet:dataCite_title", "schemeid": "dnet:dataCite_title"}, "value": "Altered brain activation in a reversal learning task unmasks adaptive changes in cognitive control in writer's cramp"}]} +{"context": [], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:datasetarchive", "classname": "sysimport:crosswalk:datasetarchive", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": true, "inferenceprovenance": "dedup-similarity-result-levenstein", "invisible": false, "trust": "0.9"}, "resourcetype": {"classid": "0004", "classname": "Conference object", "schemename": "dnet:dataCite_resource", "schemeid": "dnet:dataCite_resource"}, "pid": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "arXiv", "classname": "arXiv", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "arXivSampleID"} ,{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "doi", "classname": "doi", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "10.0001/doi2"}], "contributor": [], "resulttype": {"classid": "publication", "classname": "publication", "schemename": "dnet:result_typologies", "schemeid": "dnet:result_typologies"}, "relevantdate": [], "collectedfrom": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "CRIS UNS (Current Research Information System University of Novi Sad)", "key": "10|CRIS_UNS____::f66f1bd369679b5b077dcdf006089556"}], "id": "50|doi_________::9f9d014eea45dab432cab636c4c9cf39", "subject": [], "instance": [{"refereed": null, "hostedby": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "CRIS UNS (Current Research Information System University of Novi Sad)", "key": "10|CRIS_UNS____::f66f1bd369679b5b077dcdf006089556"}, "processingchargeamount": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "license": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "url": ["https://www.cris.uns.ac.rs/record.jsf?recordId=113444&source=OpenAIRE&language=en"], "distributionlocation": "", "processingchargecurrency": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2019-01-01"}, "collectedfrom": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "CRIS UNS (Current Research Information System University of Novi Sad)", "key": "10|CRIS_UNS____::f66f1bd369679b5b077dcdf006089556"}, "accessright": {"classid": "UNKNOWN", "classname": "UNKNOWN", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "instancetype": {"classid": "0004", "classname": "Conference object", "schemename": "dnet:dataCite_resource", "schemeid": "dnet:dataCite_resource"}}], "embargoenddate": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "lastupdatetimestamp": 0, "author": [{"surname": "Zeuner", "name": "Kirsten E.", "pid": [], "rank": 1, "affiliation": [], "fullname": "Zeuner, Kirsten E."}, {"surname": "Knutzen", "name": "Arne", "pid": [], "rank": 2, "affiliation": [], "fullname": "Knutzen, Arne"}, {"surname": "Granert", "name": "Oliver", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0002-0656-1023"}, {"qualifier": {"classid": "pubmed", "classname": "pubmed"}, "value": "pubmed.it"}], "rank": 3, "affiliation": [], "fullname": "Granert, Oliver"}, {"surname": "Sablowsky", "name": "Simone", "pid": [{"qualifier": {"classid": "id", "classname": "id"}, "value": "12345678"}], "rank": 4, "affiliation": [], "fullname": "Sablowsky, Simone"}, {"surname": "Go\\u0308tz", "name": "Julia", "pid": [], "rank": 5, "affiliation": [], "fullname": "G\\u00f6tz, Julia"}, {"surname": "Wolff", "name": "Stephan", "pid": [], "rank": 6, "affiliation": [], "fullname": "Wolff, Stephan"}, {"surname": "Jansen", "name": "Olav", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0000-0656-1023"},{"qualifier": {"classid": "id", "classname": "id"}, "value": "987654321"}], "rank": 7, "affiliation": [], "fullname": "Jansen, Olav"}, {"surname": "Dressler", "name": "Dirk", "pid": [], "rank": 8, "affiliation": [], "fullname": "Dressler, Dirk"}, {"surname": "Schneider", "name": "Susanne A.", "pid": [], "rank": 9, "affiliation": [], "fullname": "Schneider, Susanne A."}], "source": [], "dateofcollection": "2020-03-10T15:05:38.685Z", "fulltext": [], "dateoftransformation": "2020-03-11T20:11:13.15Z", "description": [], "format": [], "journal": {"issnPrinted": "", "conferencedate": "", "conferenceplace": "", "name": "", "edition": "", "iss": "", "sp": "", "vol": "", "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "issnOnline": "", "ep": "", "issnLinking": ""}, "coverage": [], "publisher": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "language": {"classid": "en", "classname": "en", "schemename": "dnet:languages", "schemeid": "dnet:languages"}, "bestaccessright": {"classid": "UNKNOWN", "classname": "not available", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "country": [{"classid": "FI", "classname": "Finland", "schemeid": "dnet:countries", "schemename": "dnet:countries"}], "extraInfo": [], "originalId": ["(BISIS)113444", "https://www.cris.uns.ac.rs/record.jsf?recordId=113444&source=OpenAIRE&language=en"], "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "title": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "test title", "classname": "test title", "schemename": "dnet:dataCite_title", "schemeid": "dnet:dataCite_title"}, "value": "Antichains of copies of ultrahomogeneous structures"}]} \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_idgeneration2.json b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_idgeneration2.json new file mode 100644 index 000000000..c37bfdef2 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_idgeneration2.json @@ -0,0 +1,3 @@ +{ "id" : "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1g", "pid" : [ { "value" : "pid1", "qualifier" : { "classid" : "original" } } ], "dateofacceptance" : { "value" : "2000-01-01"}, "collectedfrom" : [ { "key" : "key", "value" : "value" } ] } +{ "id" : "50|doi_________::1a77a3bba737f8b669dcf330ad3b37e2", "pid" : [ { "value" : "pid2", "qualifier" : { "classid" : "doi" } } ], "dateofacceptance" : { "value" : "2000-01-01"}, "collectedfrom" : [ { "key" : "key", "value" : "value" } ] } +{ "id" : "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f", "pid" : [ { "value" : "pid3", "qualifier" : { "classid" : "original" } } ], "dateofacceptance" : { "value" : "2000-01-01"}, "collectedfrom" : [ { "key" : "key", "value" : "value" } ] } \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_idgeneration3.json b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_idgeneration3.json new file mode 100644 index 000000000..f0f6c23d5 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_idgeneration3.json @@ -0,0 +1,3 @@ +{ "id" : "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1g", "pid" : [ { "value" : "pid1", "qualifier" : { "classid" : "original" } } ], "dateofacceptance" : { "value" : "2000-01-01"}, "collectedfrom" : [ { "key" : "key", "value" : "value" } ] } +{ "id" : "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1h", "pid" : [ { "value" : "pid2", "qualifier" : { "classid" : "original" } } ], "dateofacceptance" : { "value" : "2000-01-01"}, "collectedfrom" : [ { "key" : "key", "value" : "value" } ] } +{ "id" : "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1i", "pid" : [ { "value" : "pid3", "qualifier" : { "classid" : "original" } } ], "dateofacceptance" : { "value" : "2000-01-01"}, "collectedfrom" : [ { "key" : "key", "value" : "value" } ] } \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge.json b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge.json index 2e568e050..e19bef6d0 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge.json +++ b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge.json @@ -1,3 +1,3 @@ {"context": [], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:datasetarchive", "classname": "sysimport:crosswalk:datasetarchive", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "dedup-similarity-result-levenstein", "invisible": false, "trust": "0.95"}, "resourcetype": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "pid": [], "contributor": [], "resulttype": {"classid": "publication", "classname": "publication", "schemename": "dnet:result_typologies", "schemeid": "dnet:result_typologies"}, "relevantdate": [], "collectedfrom": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Journal.fi", "key": "10|openaire____::6eef8049d0feedc089ee009abca55e35"}], "id": "50|a89337edbe55::4930db9e954866d70916cbfba9f81f97", "subject": [], "instance": [{"refereed": null, "hostedby": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Journal.fi", "key": "10|openaire____::6eef8049d0feedc089ee009abca55e35"}, "processingchargeamount": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "license": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "url": [], "distributionlocation": "", "processingchargecurrency": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "collectedfrom": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Journal.fi", "key": "10|openaire____::6eef8049d0feedc089ee009abca55e35"}, "accessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "instancetype": {"classid": "0001", "classname": "Article", "schemename": "dnet:dataCite_resource", "schemeid": "dnet:dataCite_resource"}}], "embargoenddate": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "lastupdatetimestamp": 0, "author": [{"surname": "Go\\u0308tz", "name": "Julia", "pid": [], "rank": 5, "affiliation": [], "fullname": "G\\u00f6tz, Julia"}, {"surname": "Wolff", "name": "Stephan", "pid": [], "rank": 6, "affiliation": [], "fullname": "Wolff, Stephan"}, {"surname": "Jansen", "name": "Olav", "pid": [], "rank": 7, "affiliation": [], "fullname": "Jansen, Olav"}, {"surname": "Dressler", "name": "Dirk", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0000-0656-9999"},{"qualifier": {"classid": "id", "classname": "id"}, "value": "987654321"}], "rank": 8, "affiliation": [], "fullname": "Dressler, Dirk"}, {"surname": "Schneider", "name": "Susanne A.", "pid": [], "rank": 9, "affiliation": [], "fullname": "Schneider, Susanne A."}], "source": [], "dateofcollection": "2019-11-05T14:49:22.351Z", "fulltext": [], "dateoftransformation": "2019-11-05T16:10:58.988Z", "description": [], "format": [], "journal": {"issnPrinted": "1459-6067", "conferencedate": "", "conferenceplace": "", "name": "Agricultural and Food Science", "edition": "", "iss": "3", "sp": "", "vol": "27", "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "issnOnline": "1795-1895", "ep": "", "issnLinking": ""}, "coverage": [], "publisher": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "language": {"classid": "eng", "classname": "English", "schemename": "dnet:languages", "schemeid": "dnet:languages"}, "bestaccessright": {"classid": "12MONTHS", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "country": [], "extraInfo": [], "originalId": [], "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2018-09-30"}, "title": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "main title", "classname": "main title", "schemename": "dnet:dataCite_title", "schemeid": "dnet:dataCite_title"}, "value": "Altered brain activation in a reversal learning task unmasks adaptive changes in cognitive control in writer's cramp"}]} -{"context": [], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:repository", "classname": "sysimport:crosswalk:repository", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": true, "inferenceprovenance": "dedup-similarity-result-levenstein", "invisible": false, "trust": "0.9"}, "resourcetype": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "pid": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "doi", "classname": "doi", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "10.1016/j.nicl.2015.11.006"}], "contributor": [], "resulttype": {"classid": "publication", "classname": "publication", "schemename": "dnet:result_typologies", "schemeid": "dnet:result_typologies"}, "relevantdate": [], "collectedfrom": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "BASE (Open Access Aggregator)", "key": "10|openaire____::df45502607927471ecf8a6ae83683ff5"}], "id": "50|base_oa_____::0968af610a356656706657e4f234b340", "subject": [], "instance": [{"refereed": null, "hostedby": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "NeuroImage: Clinical", "key": "10|doajarticles::0c0e74daa5d95504eade9c81ebbd5b8a"}, "processingchargeamount": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "license": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "http://creativecommons.org/licenses/by-nc-nd/4.0/"}, "url": ["http://dx.doi.org/10.1016/j.nicl.2015.11.006"], "distributionlocation": "", "processingchargecurrency": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "collectedfrom": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "BASE (Open Access Aggregator)", "key": "10|openaire____::df45502607927471ecf8a6ae83683ff5"}, "accessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "instancetype": {"classid": "0001", "classname": "Article", "schemename": "dnet:publication_resource", "schemeid": "dnet:publication_resource"}}], "embargoenddate": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "lastupdatetimestamp": 0, "author": [{"surname": "Zeuner", "name": "Kirsten E.", "pid": [], "rank": 1, "affiliation": [], "fullname": "Zeuner, Kirsten E."}, {"surname": "Knutzen", "name": "Arne", "pid": [], "rank": 2, "affiliation": [], "fullname": "Knutzen, Arne"}, {"surname": "Granert", "name": "Oliver", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0002-0656-1023"}], "rank": 3, "affiliation": [], "fullname": "Granert, Oliver"}, {"surname": "Sablowsky", "name": "Simone", "pid": [], "rank": 4, "affiliation": [], "fullname": "Sablowsky, Simone"}, {"surname": "Go\\u0308tz", "name": "Julia", "pid": [], "rank": 5, "affiliation": [], "fullname": "G\\u00f6tz, Julia"}, {"surname": "Wolff", "name": "Stephan", "pid": [], "rank": 6, "affiliation": [], "fullname": "Wolff, Stephan"}, {"surname": "Jansen", "name": "Olav", "pid": [], "rank": 7, "affiliation": [], "fullname": "Jansen, Olav"}, {"surname": "Dressler", "name": "Dirk", "pid": [], "rank": 8, "affiliation": [], "fullname": "Dressler, Dirk"}, {"surname": "Schneider", "name": "Susanne A.", "pid": [], "rank": 9, "affiliation": [], "fullname": "Schneider, Susanne A."}, {"surname": "Klein", "name": "Christine", "pid": [], "rank": 10, "affiliation": [], "fullname": "Klein, Christine"}, {"surname": "Deuschl", "name": "Gu\\u0308nther", "pid": [], "rank": 11, "affiliation": [], "fullname": "Deuschl, G\\u00fcnther"}, {"surname": "Eimeren", "name": "Thilo", "pid": [], "rank": 12, "affiliation": [], "fullname": "van Eimeren, Thilo"}, {"surname": "Witt", "name": "Karsten", "pid": [], "rank": 13, "affiliation": [], "fullname": "Witt, Karsten"}], "source": [], "dateofcollection": "2017-07-27T19:04:09.131Z", "fulltext": [], "dateoftransformation": "2019-01-23T10:15:19.582Z", "description": [], "format": [], "journal": {"issnPrinted": "2213-1582", "conferencedate": "", "conferenceplace": "", "name": "NeuroImage: Clinical", "edition": "", "iss": "", "sp": "63", "vol": "10", "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "issnOnline": "", "ep": "70", "issnLinking": ""}, "coverage": [], "publisher": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Elsevier BV"}, "language": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "bestaccessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "country": [{"classid": "IT", "classname": "Italy", "schemeid": "dnet:countries", "schemename": "dnet:countries"}], "extraInfo": [], "originalId": ["10.1016/j.nicl.2015.11.006"], "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "title": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "main title", "classname": "main title", "schemename": "dnet:dataCite_title", "schemeid": "dnet:dataCite_title"}, "value": "Altered brain activation in a reversal learning task unmasks adaptive changes in cognitive control in writer's cramp"}]} +{"context": [], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:repository", "classname": "sysimport:crosswalk:repository", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": true, "inferenceprovenance": "dedup-similarity-result-levenstein", "invisible": false, "trust": "0.9"}, "resourcetype": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "pid": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "doi", "classname": "doi", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "10.1016/j.nicl.2015.11.006"}], "contributor": [], "resulttype": {"classid": "publication", "classname": "publication", "schemename": "dnet:result_typologies", "schemeid": "dnet:result_typologies"}, "relevantdate": [], "collectedfrom": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "BASE (Open Access Aggregator)", "key": "10|openaire____::df45502607927471ecf8a6ae83683ff5"}], "id": "50|doi_________::0968af610a356656706657e4f234b340", "subject": [], "instance": [{"refereed": null, "hostedby": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "NeuroImage: Clinical", "key": "10|doajarticles::0c0e74daa5d95504eade9c81ebbd5b8a"}, "processingchargeamount": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "license": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "http://creativecommons.org/licenses/by-nc-nd/4.0/"}, "url": ["http://dx.doi.org/10.1016/j.nicl.2015.11.006"], "distributionlocation": "", "processingchargecurrency": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "collectedfrom": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "BASE (Open Access Aggregator)", "key": "10|openaire____::df45502607927471ecf8a6ae83683ff5"}, "accessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "instancetype": {"classid": "0001", "classname": "Article", "schemename": "dnet:publication_resource", "schemeid": "dnet:publication_resource"}}], "embargoenddate": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "lastupdatetimestamp": 0, "author": [{"surname": "Zeuner", "name": "Kirsten E.", "pid": [], "rank": 1, "affiliation": [], "fullname": "Zeuner, Kirsten E."}, {"surname": "Knutzen", "name": "Arne", "pid": [], "rank": 2, "affiliation": [], "fullname": "Knutzen, Arne"}, {"surname": "Granert", "name": "Oliver", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0002-0656-1023"}], "rank": 3, "affiliation": [], "fullname": "Granert, Oliver"}, {"surname": "Sablowsky", "name": "Simone", "pid": [], "rank": 4, "affiliation": [], "fullname": "Sablowsky, Simone"}, {"surname": "Go\\u0308tz", "name": "Julia", "pid": [], "rank": 5, "affiliation": [], "fullname": "G\\u00f6tz, Julia"}, {"surname": "Wolff", "name": "Stephan", "pid": [], "rank": 6, "affiliation": [], "fullname": "Wolff, Stephan"}, {"surname": "Jansen", "name": "Olav", "pid": [], "rank": 7, "affiliation": [], "fullname": "Jansen, Olav"}, {"surname": "Dressler", "name": "Dirk", "pid": [], "rank": 8, "affiliation": [], "fullname": "Dressler, Dirk"}, {"surname": "Schneider", "name": "Susanne A.", "pid": [], "rank": 9, "affiliation": [], "fullname": "Schneider, Susanne A."}, {"surname": "Klein", "name": "Christine", "pid": [], "rank": 10, "affiliation": [], "fullname": "Klein, Christine"}, {"surname": "Deuschl", "name": "Gu\\u0308nther", "pid": [], "rank": 11, "affiliation": [], "fullname": "Deuschl, G\\u00fcnther"}, {"surname": "Eimeren", "name": "Thilo", "pid": [], "rank": 12, "affiliation": [], "fullname": "van Eimeren, Thilo"}, {"surname": "Witt", "name": "Karsten", "pid": [], "rank": 13, "affiliation": [], "fullname": "Witt, Karsten"}], "source": [], "dateofcollection": "2017-07-27T19:04:09.131Z", "fulltext": [], "dateoftransformation": "2019-01-23T10:15:19.582Z", "description": [], "format": [], "journal": {"issnPrinted": "2213-1582", "conferencedate": "", "conferenceplace": "", "name": "NeuroImage: Clinical", "edition": "", "iss": "", "sp": "63", "vol": "10", "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "issnOnline": "", "ep": "70", "issnLinking": ""}, "coverage": [], "publisher": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Elsevier BV"}, "language": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "bestaccessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "country": [{"classid": "IT", "classname": "Italy", "schemeid": "dnet:countries", "schemename": "dnet:countries"}], "extraInfo": [], "originalId": ["10.1016/j.nicl.2015.11.006"], "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "title": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "main title", "classname": "main title", "schemename": "dnet:dataCite_title", "schemeid": "dnet:dataCite_title"}, "value": "Altered brain activation in a reversal learning task unmasks adaptive changes in cognitive control in writer's cramp"}]} {"context": [], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:datasetarchive", "classname": "sysimport:crosswalk:datasetarchive", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": true, "inferenceprovenance": "dedup-similarity-result-levenstein", "invisible": false, "trust": "0.9"}, "resourcetype": {"classid": "0004", "classname": "Conference object", "schemename": "dnet:dataCite_resource", "schemeid": "dnet:dataCite_resource"}, "pid": [], "contributor": [], "resulttype": {"classid": "publication", "classname": "publication", "schemename": "dnet:result_typologies", "schemeid": "dnet:result_typologies"}, "relevantdate": [], "collectedfrom": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "CRIS UNS (Current Research Information System University of Novi Sad)", "key": "10|CRIS_UNS____::f66f1bd369679b5b077dcdf006089556"}], "id": "50|CrisUnsNoviS::9f9d014eea45dab432cab636c4c9cf39", "subject": [], "instance": [{"refereed": null, "hostedby": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "CRIS UNS (Current Research Information System University of Novi Sad)", "key": "10|CRIS_UNS____::f66f1bd369679b5b077dcdf006089556"}, "processingchargeamount": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "license": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "url": ["https://www.cris.uns.ac.rs/record.jsf?recordId=113444&source=OpenAIRE&language=en"], "distributionlocation": "", "processingchargecurrency": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2019-01-01"}, "collectedfrom": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "CRIS UNS (Current Research Information System University of Novi Sad)", "key": "10|CRIS_UNS____::f66f1bd369679b5b077dcdf006089556"}, "accessright": {"classid": "UNKNOWN", "classname": "UNKNOWN", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "instancetype": {"classid": "0004", "classname": "Conference object", "schemename": "dnet:dataCite_resource", "schemeid": "dnet:dataCite_resource"}}], "embargoenddate": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "lastupdatetimestamp": 0, "author": [{"surname": "Zeuner", "name": "Kirsten E.", "pid": [], "rank": 1, "affiliation": [], "fullname": "Zeuner, Kirsten E."}, {"surname": "Knutzen", "name": "Arne", "pid": [], "rank": 2, "affiliation": [], "fullname": "Knutzen, Arne"}, {"surname": "Granert", "name": "Oliver", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0002-0656-1023"}, {"qualifier": {"classid": "pubmed", "classname": "pubmed"}, "value": "pubmed.it"}], "rank": 3, "affiliation": [], "fullname": "Granert, Oliver"}, {"surname": "Sablowsky", "name": "Simone", "pid": [{"qualifier": {"classid": "id", "classname": "id"}, "value": "12345678"}], "rank": 4, "affiliation": [], "fullname": "Sablowsky, Simone"}, {"surname": "Go\\u0308tz", "name": "Julia", "pid": [], "rank": 5, "affiliation": [], "fullname": "G\\u00f6tz, Julia"}, {"surname": "Wolff", "name": "Stephan", "pid": [], "rank": 6, "affiliation": [], "fullname": "Wolff, Stephan"}, {"surname": "Jansen", "name": "Olav", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0000-0656-1023"},{"qualifier": {"classid": "id", "classname": "id"}, "value": "987654321"}], "rank": 7, "affiliation": [], "fullname": "Jansen, Olav"}, {"surname": "Dressler", "name": "Dirk", "pid": [], "rank": 8, "affiliation": [], "fullname": "Dressler, Dirk"}, {"surname": "Schneider", "name": "Susanne A.", "pid": [], "rank": 9, "affiliation": [], "fullname": "Schneider, Susanne A."}], "source": [], "dateofcollection": "2020-03-10T15:05:38.685Z", "fulltext": [], "dateoftransformation": "2020-03-11T20:11:13.15Z", "description": [], "format": [], "journal": {"issnPrinted": "", "conferencedate": "", "conferenceplace": "", "name": "", "edition": "", "iss": "", "sp": "", "vol": "", "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "issnOnline": "", "ep": "", "issnLinking": ""}, "coverage": [], "publisher": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "language": {"classid": "en", "classname": "en", "schemename": "dnet:languages", "schemeid": "dnet:languages"}, "bestaccessright": {"classid": "UNKNOWN", "classname": "not available", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "country": [{"classid": "FI", "classname": "Finland", "schemeid": "dnet:countries", "schemename": "dnet:countries"}], "extraInfo": [], "originalId": ["(BISIS)113444", "https://www.cris.uns.ac.rs/record.jsf?recordId=113444&source=OpenAIRE&language=en"], "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "title": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "test title", "classname": "test title", "schemename": "dnet:dataCite_title", "schemeid": "dnet:dataCite_title"}, "value": "Antichains of copies of ultrahomogeneous structures"}]} \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge2.json b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge2.json index a7937c287..21c436085 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge2.json +++ b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge2.json @@ -1,4 +1,3 @@ -{"id":"50|doajarticles::842fa3b99fcdccafb4d5c8a815f56efa","dateofcollection":"2020-04-06T12:22:31.216Z","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Korean Continence Society"},"bestaccessright":null,"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":null,"key":"10|driver______::bee53aa31dc2cbb538c10c2b65fa5824","value":"DOAJ-Articles"}],"pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"doi","classname":"doi","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"10.5213/inj.1632552.276"}],"author":[{"affiliation":null,"fullname":"Seok Joong Yun","name":null,"pid":[],"rank":1,"surname":null},{"affiliation":null,"fullname":"Pildu Jeong","name":null,"pid":[],"rank":2,"surname":null},{"affiliation":null,"fullname":"Ho Won Kang","name":null,"pid":[],"rank":3,"surname":null},{"affiliation":null,"fullname":"Helen Ki Shinn","name":null,"pid":[],"rank":4,"surname":null},{"affiliation":null,"fullname":"Ye-Hwan Kim","name":null,"pid":[],"rank":5,"surname":null},{"affiliation":null,"fullname":"Chunri Yan","name":null,"pid":[],"rank":6,"surname":null},{"affiliation":null,"fullname":"Young-Ki Choi","name":null,"pid":[],"rank":7,"surname":null},{"affiliation":null,"fullname":"Dongho Kim","name":null,"pid":[],"rank":8,"surname":null},{"affiliation":null,"fullname":"Dong Hee Ryu","name":null,"pid":[],"rank":9,"surname":null},{"affiliation":null,"fullname":"Yun-Sok Ha","name":null,"pid":[],"rank":10,"surname":null},{"affiliation":null,"fullname":"Tae-Hwan Kim","name":null,"pid":[],"rank":11,"surname":null},{"affiliation":null,"fullname":"Tae Gyun Kwon","name":null,"pid":[],"rank":12,"surname":null},{"affiliation":null,"fullname":"Jung Min Kim","name":null,"pid":[],"rank":13,"surname":null},{"affiliation":null,"fullname":"Sang Heon Suh","name":null,"pid":[],"rank":14,"surname":null},{"affiliation":null,"fullname":"Seon-Kyu Kim","name":null,"pid":[],"rank":15,"surname":null},{"affiliation":null,"fullname":"Seon-Young Kim","name":null,"pid":[],"rank":16,"surname":null},{"affiliation":null,"fullname":"Sang Tae Kim","name":null,"pid":[],"rank":17,"surname":null},{"affiliation":null,"fullname":"Won Tae Kim","name":null,"pid":[],"rank":18,"surname":null},{"affiliation":null,"fullname":"Ok-Jun Lee","name":null,"pid":[],"rank":19,"surname":null},{"affiliation":null,"fullname":"Sung-Kwon Moon","name":null,"pid":[],"rank":20,"surname":null},{"affiliation":null,"fullname":"Nam-Hyung Kim","name":null,"pid":[],"rank":21,"surname":null},{"affiliation":null,"fullname":"Isaac Yi Kim","name":null,"pid":[],"rank":22,"surname":null},{"affiliation":null,"fullname":"Jayoung Kim","name":null,"pid":[],"rank":23,"surname":null},{"affiliation":null,"fullname":"Hee-Jae Cha","name":null,"pid":[],"rank":24,"surname":null},{"affiliation":null,"fullname":"Yung-Hyun Choi","name":null,"pid":[],"rank":25,"surname":null},{"affiliation":null,"fullname":"Eun-Jong Cha","name":null,"pid":[],"rank":26,"surname":null},{"affiliation":null,"fullname":"Wun-Jae Kim","name":null,"pid":[],"rank":27,"surname":null}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"eng","classname":"English","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"MicroRNAs"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Neoplasms"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Herpesviridae"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Hyperplasia"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Diseases of the genitourinary system. Urology"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"RC870-923"}],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Purpose: Previously, we reported the presence of virus-encoded microRNAs (miRNAs) in the urine of prostate cancer (CaP) patients. In this study, we investigated the expression of two herpes virus-encoded miRNAs in prostate tissue. Methods: A total of 175 tissue samples from noncancerous benign prostatic hyperplasia (BPH), 248 tissue samples from patients with CaP and BPH, and 50 samples from noncancerous surrounding tissues from these same patients were analyzed for the expression of two herpes virus-encoded miRNAs by real-time polymerase chain reaction (PCR) and immunocytochemistry using nanoparticles as molecular beacons. Results: Real-time reverse transcription-PCR results revealed significantly higher expression of hsv1-miR-H18 and hsv2-miRH9- 5p in surrounding noncancerous and CaP tissues than that in BPH tissue (each comparison, P<0.001). Of note, these miRNA were expressed equivalently in the CaP tissues and surrounding noncancerous tissues. Moreover, immunocytochemistry clearly demonstrated a significant enrichment of both hsv1-miR-H18 and hsv2-miR-H9 beacon-labeled cells in CaP and surrounding noncancerous tissue compared to that in BPH tissue (each comparison, P<0.05 for hsv1-miR-H18 and hsv2- miR-H9). Conclusions: These results suggest that increased expression of hsv1-miR-H18 and hsv2-miR-H95p might be associated with tumorigenesis in the prostate. Further studies will be required to elucidate the role of these miRNAs with respect to CaP and herpes viral infections."}],"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"embargoenddate":null,"resourcetype":null,"context":[],"instance":[{"accessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"collectedfrom":{"dataInfo":null,"key":"10|driver______::bee53aa31dc2cbb538c10c2b65fa5824","value":"DOAJ-Articles"},"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"distributionlocation":"","hostedby":{"dataInfo":null,"key":"10|doajarticles::52db9a4f8e176f6e8e1d9f0c1e0a2de0","value":"International Neurourology Journal"},"instancetype":{"classid":"0001","classname":"peerReviewed","schemeid":"dnet:publication_resource","schemename":"dnet:publication_resource"},"license":null,"processingchargeamount":null,"processingchargecurrency":null,"refereed":null,"url":["http://www.einj.org/upload/pdf/inj-1632552-276.pdf","https://doaj.org/toc/2093-4777","https://doaj.org/toc/2093-6931"]}]} -{"id":"50|od_______267::b5f5da11a8239ef57655cea8675cb466","dateofcollection":"","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Korean Continence Society"},"bestaccessright":null,"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":null,"key":"10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357","value":"PubMed Central"}],"pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"doi","classname":"doi","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"10.5213/inj.1632552.276"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"pmc","classname":"pmc","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"PMC4932644"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"pmid","classname":"pmid","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"27377944"}],"author":[{"affiliation":null,"fullname":"Yun, Seok Joong","name":"Seok Joong","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-7737-4746"}],"rank":1,"surname":"Yun"},{"affiliation":null,"fullname":"Jeong, Pildu","name":"Pildu","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-5602-5376"}],"rank":2,"surname":"Jeong"},{"affiliation":null,"fullname":"Kang, Ho Won","name":"Ho Won","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8164-4427"}],"rank":3,"surname":"Kang"},{"affiliation":null,"fullname":"Shinn, Helen Ki","name":"Helen Ki","pid":[],"rank":4,"surname":"Shinn"},{"affiliation":null,"fullname":"Kim, Ye-Hwan","name":"Ye-Hwan","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8676-7119"}],"rank":5,"surname":"Kim"},{"affiliation":null,"fullname":"Yan, Chunri","name":"Chunri","pid":[],"rank":6,"surname":"Yan"},{"affiliation":null,"fullname":"Choi, Young-Ki","name":"Young-Ki","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1894-9869"}],"rank":7,"surname":"Choi"},{"affiliation":null,"fullname":"Kim, Dongho","name":"Dongho","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1409-3311"}],"rank":8,"surname":"Kim"},{"affiliation":null,"fullname":"Ryu, Dong Hee","name":"Dong Hee","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-6088-298X"}],"rank":9,"surname":"Ryu"},{"affiliation":null,"fullname":"Ha, Yun-Sok","name":"Yun-Sok","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0003-3732-9814"}],"rank":10,"surname":"Ha"},{"affiliation":null,"fullname":"Kim, Tae-Hwan","name":"Tae-Hwan","pid":[],"rank":11,"surname":"Kim"},{"affiliation":null,"fullname":"Kwon, Tae Gyun","name":"Tae Gyun","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-4390-0952"}],"rank":12,"surname":"Kwon"},{"affiliation":null,"fullname":"Kim, Jung Min","name":"Jung Min","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-6319-0217"}],"rank":13,"surname":"Kim"},{"affiliation":null,"fullname":"Suh, Sang Heon","name":"Sang Heon","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0003-4560-8880"}],"rank":14,"surname":"Suh"},{"affiliation":null,"fullname":"Kim, Seon-Kyu","name":"Seon-Kyu","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-4176-5187"}],"rank":15,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Seon-Young","name":"Seon-Young","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1030-7730"}],"rank":16,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Sang Tae","name":"Sang Tae","pid":[],"rank":17,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Won Tae","name":"Won Tae","pid":[],"rank":18,"surname":"Kim"},{"affiliation":null,"fullname":"Lee, Ok-Jun","name":"Ok-Jun","pid":[],"rank":19,"surname":"Lee"},{"affiliation":null,"fullname":"Moon, Sung-Kwon","name":"Sung-Kwon","pid":[],"rank":20,"surname":"Moon"},{"affiliation":null,"fullname":"Kim, Nam-Hyung","name":"Nam-Hyung","pid":[],"rank":21,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Isaac Yi","name":"Isaac Yi","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1967-5281"}],"rank":22,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Jayoung","name":"Jayoung","pid":[],"rank":23,"surname":"Kim"},{"affiliation":null,"fullname":"Cha, Hee-Jae","name":"Hee-Jae","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-6963-2685"}],"rank":24,"surname":"Cha"},{"affiliation":null,"fullname":"Choi, Yung-Hyun","name":"Yung-Hyun","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1454-3124"}],"rank":25,"surname":"Choi"},{"affiliation":null,"fullname":"Cha, Eun-Jong","name":"Eun-Jong","pid":[],"rank":26,"surname":"Cha"},{"affiliation":null,"fullname":"Kim, Wun-Jae","name":"Wun-Jae","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8060-8926"}],"rank":27,"surname":"Kim"}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"eng","classname":"English","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Original Article"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Fundamental Science for Neurourology"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"MicroRNAs"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Neoplasms"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Herpesviridae"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Hyperplasia"}],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Purpose: Previously, we reported the presence of virus-encoded microRNAs (miRNAs) in the urine of prostate cancer (CaP) patients. In this study, we investigated the expression of two herpes virus-encoded miRNAs in prostate tissue. Methods: A total of 175 tissue samples from noncancerous benign prostatic hyperplasia (BPH), 248 tissue samples from patients with CaP and BPH, and 50 samples from noncancerous surrounding tissues from these same patients were analyzed for the expression of two herpes virus-encoded miRNAs by real-time polymerase chain reaction (PCR) and immunocytochemistry using nanoparticles as molecular beacons. Results: Real-time reverse transcription-PCR results revealed significantly higher expression of hsv1-miR-H18 and hsv2-miRH9- 5p in surrounding noncancerous and CaP tissues than that in BPH tissue (each comparison, P<0.001). Of note, these miRNA were expressed equivalently in the CaP tissues and surrounding noncancerous tissues. Moreover, immunocytochemistry clearly demonstrated a significant enrichment of both hsv1-miR-H18 and hsv2-miR-H9 beacon-labeled cells in CaP and surrounding noncancerous tissue compared to that in BPH tissue (each comparison, P<0.05 for hsv1-miR-H18 and hsv2- miR-H9). Conclusions: These results suggest that increased expression of hsv1-miR-H18 and hsv2-miR-H95p might be associated with tumorigenesis in the prostate. Further studies will be required to elucidate the role of these miRNAs with respect to CaP and herpes viral infections."}],"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"embargoenddate":null,"resourcetype":null,"context":[],"instance":[{"accessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"collectedfrom":{"dataInfo":null,"key":"10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357","value":"PubMed Central"},"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"distributionlocation":"","hostedby":{"dataInfo":null,"key":"10|opendoar____::8b6dd7db9af49e67306feb59a8bdc52c","value":"Europe PubMed Central"},"instancetype":{"classid":"0001","classname":"peerReviewed","schemeid":"dnet:publication_resource","schemename":"dnet:publication_resource"},"license":null,"processingchargeamount":null,"processingchargecurrency":null,"refereed":null,"url":["http://europepmc.org/articles/PMC4932644"]}]} -{"id":"50|doiboost____::0ca46ff10b2b4c756191719d85302b14","dateofcollection":"2019-02-15","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":""},"bestaccessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:actionset","classname":"sysimport:actionset","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::081b82f96300b6a6e3d282bad31cb6e2","value":"Crossref"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::5f532a3fc4f1ea403f37070f59a7a53a","value":"Microsoft Academic Graph"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::8ac8380272269217cb09a928c8caa993","value":"UnpayWall"}],"pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"qualifier":{"classid":"doi","classname":"doi","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"10.5213/inj.1632552.276"}],"author":[{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Seok Joong Yun","name":"Seok Joong","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2105974574"}],"rank":1,"surname":"Yun"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Pildu Jeong","name":"Pildu","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2041919263"}],"rank":2,"surname":"Jeong"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ho Won Kang","name":"Ho Won","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2164408067"}],"rank":3,"surname":"Kang"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Inha University"}],"fullname":"Helen Ki Shinn","name":"Helen Ki","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2045077081"}],"rank":4,"surname":"Shinn"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ye-Hwan Kim","name":"Ye-Hwan","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2276303457"}],"rank":5,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Chunri Yan","name":"Chunri","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2186750404"}],"rank":6,"surname":"Yan"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Young-Ki Choi","name":"Young-Ki","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2311466124"}],"rank":7,"surname":"Choi"},{"affiliation":[],"fullname":"Dongho Kim","name":"Dongho","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2644843893"}],"rank":8,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Dong Hee Ryu","name":"Dong Hee","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2117604941"}],"rank":9,"surname":"Ryu"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Yun-Sok Ha","name":"Yun-Sok","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2145233282"}],"rank":10,"surname":"Ha"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Tae-Hwan Kim","name":"Tae-Hwan","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2509096378"}],"rank":11,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Tae Gyun Kwon","name":"Tae Gyun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"1978978081"}],"rank":12,"surname":"Kwon"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Daejeon University"}],"fullname":"Jung Min Kim","name":"Jung Min","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2265841962"}],"rank":13,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"KAIST"}],"fullname":"Sang Heon Suh","name":"Sang Heon","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2890693470"}],"rank":14,"surname":"Suh"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Korea Research Institute of Bioscience and Biotechnology"}],"fullname":"Seon-Kyu Kim","name":"Seon-Kyu","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2162364977"}],"rank":15,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Korea Research Institute of Bioscience and Biotechnology"}],"fullname":"Seon-Young Kim","name":"Seon-Young","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2344797375"}],"rank":16,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Seoul National University Bundang Hospital"}],"fullname":"Sang Tae Kim","name":"Sang Tae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2257827509"}],"rank":17,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Won Tae Kim","name":"Won Tae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2617237649"}],"rank":18,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ok-Jun Lee","name":"Ok-Jun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2112231548"}],"rank":19,"surname":"Lee"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chung-Ang University"}],"fullname":"Sung-Kwon Moon","name":"Sung-Kwon","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2796689429"}],"rank":20,"surname":"Moon"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Nam-Hyung Kim","name":"Nam-Hyung","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2136287741"}],"rank":21,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Rutgers University"}],"fullname":"Isaac Yi Kim","name":"Isaac Yi","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2015295992"}],"rank":22,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Harvard University"}],"fullname":"Jayoung Kim","name":"Jayoung","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2130848131"}],"rank":23,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kosin University"}],"fullname":"Hee-Jae Cha","name":"Hee-Jae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2113489867"}],"rank":24,"surname":"Cha"},{"affiliation":[],"fullname":"Yung-Hyun Choi","name":"Yung-Hyun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2151282194"}],"rank":25,"surname":"Choi"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Eun-Jong Cha","name":"Eun-Jong","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2109572239"}],"rank":26,"surname":"Cha"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Wun-Jae Kim","name":"Wun-Jae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2113339670"}],"rank":27,"surname":"Kim"}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"und","classname":"Undetermined","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Purpose:"}],"dateofacceptance":null,"embargoenddate":null,"resourcetype":null,"context":null,"instance":null} -{"id":"Previously, we reported the presence of virus-encoded microRNAs (miRNAs) in the urine of prostate cancer (CaP) patients. In this study, we investigated the expression of two herpes virus-encoded miRNAs in prostate tissue.","dateofcollection":"false\u0004\u0004false\u0004false\u0004\u0005\u0005\u0005\u0004\u00032016-6-30","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":null,"inferred":null,"invisible":null,"provenanceaction":null,"trust":null},"qualifier":{"classid":"","classname":null,"schemeid":null,"schemename":null},"value":"false"},{"dataInfo":{"deletedbyinference":null,"inferenceprovenance":null,"inferred":null,"invisible":null,"provenanceaction":null,"trust":null},"qualifier":null,"value":null}],"publisher":{"dataInfo":{"deletedbyinference":null,"inferenceprovenance":null,"inferred":null,"invisible":null,"provenanceaction":null,"trust":null},"value":""},"bestaccessright":{"classid":"","classname":null,"schemeid":null,"schemename":null},"dataInfo":{"deletedbyinference":null,"inferenceprovenance":"UNKNOWN\u0005not available\u0005dnet:access_modes\u0005dnet:access_modes\u0004false\u0006\u0006false\u0006false\u0006\u0007\u0007\u0007\u0006\u000510|openaire____::081b82f96300b6a6e3d282bad31cb6e2\u0005Crossref\u0004false\u0006\u0006false\u0006false\u0006\u0007\u0007\u0007\u0006\u0005\u0004\u0004false\u0006\u0006false\u0006false\u0006\u0007\u0007\u0007\u0006\u000510|openaire____::55045bd2a65019fd8e6741a755395c8c\u0005Unknown Repository\u00040001\u0005Article\u0005dnet:publication_resource\u0005dnet:publication_resource\u0004false\u0006\u0006false\u0006false\u0006\u0007\u0007\u0007\u0006\u0005\u0004false\u0006\u0006false\u0006false\u0006\u0007\u0007\u0007\u0006\u0005\u0004false\u0006\u0006false\u0006false\u0006\u0007\u0007\u0007\u0006\u0005\u0004false\u0006\u0006false\u0006false\u0006\u0007\u0007\u0007\u0006\u0005\u0004http://einj.org/upload/pdf/inj-1632552-276.pdf","inferred":null,"invisible":null,"provenanceaction":{"classid":"UNKNOWN\u0005not available\u0005dnet:access_modes\u0005dnet:access_modes","classname":"false\u0006\u0006false\u0006false\u0006\u0007\u0007\u0007\u0006\u000510|openaire____::5f532a3fc4f1ea403f37070f59a7a53a\u0005Microsoft Academic Graph","schemeid":"false\u0006\u0006false\u0006false\u0006\u0007\u0007\u0007\u0006\u0005","schemename":""},"trust":"RESTRICTED\u0005Restricted\u0005dnet:access_modes\u0005dnet:access_modes\u0004false\u0006\u0006false\u0006false\u0006\u0007\u0007\u0007\u0006\u000510|openaire____::081b82f96300b6a6e3d282bad31cb6e2\u0005Crossref\u0004false\u0006\u0006false\u0006false\u0006\u0007\u0007\u0007\u0006\u0005\u0004\u0004false\u0006\u0006false\u0006false\u0006\u0007\u0007\u0007\u0006\u000510|doajarticles::52db9a4f8e176f6e8e1d9f0c1e0a2de0\u0005International Neurourology Journal\u00040001\u0005Article\u0005dnet:publication_resource\u0005dnet:publication_resource\u0004false\u0006\u0006false\u0006false\u0006\u0007\u0007\u0007\u0006\u0005\u0004false\u0006\u0006false\u0006false\u0006\u0007\u0007\u0007\u0006\u0005\u0004false\u0006\u0006false\u0006false\u0006\u0007\u0007\u0007\u0006\u0005\u0004false\u0006\u0006false\u0006false\u0006\u0007\u0007\u0007\u0006\u0005\u0004http://dx.doi.org/10.5213/inj.1632552.276"},"collectedfrom":null,"pid":null,"author":null,"resulttype":null,"language":null,"country":null,"subject":null,"description":null,"dateofacceptance":null,"embargoenddate":null,"resourcetype":null,"context":null,"instance":null} \ No newline at end of file +{"id":"50|doi_________::842fa3b99fcdccafb4d5c8a815f56efa","dateofcollection":"2020-04-06T12:22:31.216Z","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Korean Continence Society"},"bestaccessright":null,"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":null,"key":"10|driver______::bee53aa31dc2cbb538c10c2b65fa5824","value":"DOAJ-Articles"}],"pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"doi","classname":"doi","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"10.5213/inj.1632552.276"}],"author":[{"affiliation":null,"fullname":"Seok Joong Yun","name":null,"pid":[],"rank":1,"surname":null},{"affiliation":null,"fullname":"Pildu Jeong","name":null,"pid":[],"rank":2,"surname":null},{"affiliation":null,"fullname":"Ho Won Kang","name":null,"pid":[],"rank":3,"surname":null},{"affiliation":null,"fullname":"Helen Ki Shinn","name":null,"pid":[],"rank":4,"surname":null},{"affiliation":null,"fullname":"Ye-Hwan Kim","name":null,"pid":[],"rank":5,"surname":null},{"affiliation":null,"fullname":"Chunri Yan","name":null,"pid":[],"rank":6,"surname":null},{"affiliation":null,"fullname":"Young-Ki Choi","name":null,"pid":[],"rank":7,"surname":null},{"affiliation":null,"fullname":"Dongho Kim","name":null,"pid":[],"rank":8,"surname":null},{"affiliation":null,"fullname":"Dong Hee Ryu","name":null,"pid":[],"rank":9,"surname":null},{"affiliation":null,"fullname":"Yun-Sok Ha","name":null,"pid":[],"rank":10,"surname":null},{"affiliation":null,"fullname":"Tae-Hwan Kim","name":null,"pid":[],"rank":11,"surname":null},{"affiliation":null,"fullname":"Tae Gyun Kwon","name":null,"pid":[],"rank":12,"surname":null},{"affiliation":null,"fullname":"Jung Min Kim","name":null,"pid":[],"rank":13,"surname":null},{"affiliation":null,"fullname":"Sang Heon Suh","name":null,"pid":[],"rank":14,"surname":null},{"affiliation":null,"fullname":"Seon-Kyu Kim","name":null,"pid":[],"rank":15,"surname":null},{"affiliation":null,"fullname":"Seon-Young Kim","name":null,"pid":[],"rank":16,"surname":null},{"affiliation":null,"fullname":"Sang Tae Kim","name":null,"pid":[],"rank":17,"surname":null},{"affiliation":null,"fullname":"Won Tae Kim","name":null,"pid":[],"rank":18,"surname":null},{"affiliation":null,"fullname":"Ok-Jun Lee","name":null,"pid":[],"rank":19,"surname":null},{"affiliation":null,"fullname":"Sung-Kwon Moon","name":null,"pid":[],"rank":20,"surname":null},{"affiliation":null,"fullname":"Nam-Hyung Kim","name":null,"pid":[],"rank":21,"surname":null},{"affiliation":null,"fullname":"Isaac Yi Kim","name":null,"pid":[],"rank":22,"surname":null},{"affiliation":null,"fullname":"Jayoung Kim","name":null,"pid":[],"rank":23,"surname":null},{"affiliation":null,"fullname":"Hee-Jae Cha","name":null,"pid":[],"rank":24,"surname":null},{"affiliation":null,"fullname":"Yung-Hyun Choi","name":null,"pid":[],"rank":25,"surname":null},{"affiliation":null,"fullname":"Eun-Jong Cha","name":null,"pid":[],"rank":26,"surname":null},{"affiliation":null,"fullname":"Wun-Jae Kim","name":null,"pid":[],"rank":27,"surname":null}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"eng","classname":"English","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"MicroRNAs"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Neoplasms"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Herpesviridae"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Hyperplasia"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Diseases of the genitourinary system. Urology"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"RC870-923"}],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Purpose: Previously, we reported the presence of virus-encoded microRNAs (miRNAs) in the urine of prostate cancer (CaP) patients. In this study, we investigated the expression of two herpes virus-encoded miRNAs in prostate tissue. Methods: A total of 175 tissue samples from noncancerous benign prostatic hyperplasia (BPH), 248 tissue samples from patients with CaP and BPH, and 50 samples from noncancerous surrounding tissues from these same patients were analyzed for the expression of two herpes virus-encoded miRNAs by real-time polymerase chain reaction (PCR) and immunocytochemistry using nanoparticles as molecular beacons. Results: Real-time reverse transcription-PCR results revealed significantly higher expression of hsv1-miR-H18 and hsv2-miRH9- 5p in surrounding noncancerous and CaP tissues than that in BPH tissue (each comparison, P<0.001). Of note, these miRNA were expressed equivalently in the CaP tissues and surrounding noncancerous tissues. Moreover, immunocytochemistry clearly demonstrated a significant enrichment of both hsv1-miR-H18 and hsv2-miR-H9 beacon-labeled cells in CaP and surrounding noncancerous tissue compared to that in BPH tissue (each comparison, P<0.05 for hsv1-miR-H18 and hsv2- miR-H9). Conclusions: These results suggest that increased expression of hsv1-miR-H18 and hsv2-miR-H95p might be associated with tumorigenesis in the prostate. Further studies will be required to elucidate the role of these miRNAs with respect to CaP and herpes viral infections."}],"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"embargoenddate":null,"resourcetype":null,"context":[],"instance":[{"accessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"collectedfrom":{"dataInfo":null,"key":"10|driver______::bee53aa31dc2cbb538c10c2b65fa5824","value":"DOAJ-Articles"},"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"distributionlocation":"","hostedby":{"dataInfo":null,"key":"10|doajarticles::52db9a4f8e176f6e8e1d9f0c1e0a2de0","value":"International Neurourology Journal"},"instancetype":{"classid":"0001","classname":"peerReviewed","schemeid":"dnet:publication_resource","schemename":"dnet:publication_resource"},"license":null,"processingchargeamount":null,"processingchargecurrency":null,"refereed":null,"url":["http://www.einj.org/upload/pdf/inj-1632552-276.pdf","https://doaj.org/toc/2093-4777","https://doaj.org/toc/2093-6931"]}]} +{"id":"50|doi_________::b5f5da11a8239ef57655cea8675cb466","dateofcollection":"","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Korean Continence Society"},"bestaccessright":null,"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":null,"key":"10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357","value":"PubMed Central"}],"pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"doi","classname":"doi","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"10.5213/inj.1632552.276"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"pmc","classname":"pmc","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"PMC4932644"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"pmid","classname":"pmid","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"27377944"}],"author":[{"affiliation":null,"fullname":"Yun, Seok Joong","name":"Seok Joong","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-7737-4746"}],"rank":1,"surname":"Yun"},{"affiliation":null,"fullname":"Jeong, Pildu","name":"Pildu","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-5602-5376"}],"rank":2,"surname":"Jeong"},{"affiliation":null,"fullname":"Kang, Ho Won","name":"Ho Won","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8164-4427"}],"rank":3,"surname":"Kang"},{"affiliation":null,"fullname":"Shinn, Helen Ki","name":"Helen Ki","pid":[],"rank":4,"surname":"Shinn"},{"affiliation":null,"fullname":"Kim, Ye-Hwan","name":"Ye-Hwan","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8676-7119"}],"rank":5,"surname":"Kim"},{"affiliation":null,"fullname":"Yan, Chunri","name":"Chunri","pid":[],"rank":6,"surname":"Yan"},{"affiliation":null,"fullname":"Choi, Young-Ki","name":"Young-Ki","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1894-9869"}],"rank":7,"surname":"Choi"},{"affiliation":null,"fullname":"Kim, Dongho","name":"Dongho","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1409-3311"}],"rank":8,"surname":"Kim"},{"affiliation":null,"fullname":"Ryu, Dong Hee","name":"Dong Hee","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-6088-298X"}],"rank":9,"surname":"Ryu"},{"affiliation":null,"fullname":"Ha, Yun-Sok","name":"Yun-Sok","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0003-3732-9814"}],"rank":10,"surname":"Ha"},{"affiliation":null,"fullname":"Kim, Tae-Hwan","name":"Tae-Hwan","pid":[],"rank":11,"surname":"Kim"},{"affiliation":null,"fullname":"Kwon, Tae Gyun","name":"Tae Gyun","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-4390-0952"}],"rank":12,"surname":"Kwon"},{"affiliation":null,"fullname":"Kim, Jung Min","name":"Jung Min","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-6319-0217"}],"rank":13,"surname":"Kim"},{"affiliation":null,"fullname":"Suh, Sang Heon","name":"Sang Heon","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0003-4560-8880"}],"rank":14,"surname":"Suh"},{"affiliation":null,"fullname":"Kim, Seon-Kyu","name":"Seon-Kyu","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-4176-5187"}],"rank":15,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Seon-Young","name":"Seon-Young","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1030-7730"}],"rank":16,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Sang Tae","name":"Sang Tae","pid":[],"rank":17,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Won Tae","name":"Won Tae","pid":[],"rank":18,"surname":"Kim"},{"affiliation":null,"fullname":"Lee, Ok-Jun","name":"Ok-Jun","pid":[],"rank":19,"surname":"Lee"},{"affiliation":null,"fullname":"Moon, Sung-Kwon","name":"Sung-Kwon","pid":[],"rank":20,"surname":"Moon"},{"affiliation":null,"fullname":"Kim, Nam-Hyung","name":"Nam-Hyung","pid":[],"rank":21,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Isaac Yi","name":"Isaac Yi","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1967-5281"}],"rank":22,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Jayoung","name":"Jayoung","pid":[],"rank":23,"surname":"Kim"},{"affiliation":null,"fullname":"Cha, Hee-Jae","name":"Hee-Jae","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-6963-2685"}],"rank":24,"surname":"Cha"},{"affiliation":null,"fullname":"Choi, Yung-Hyun","name":"Yung-Hyun","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1454-3124"}],"rank":25,"surname":"Choi"},{"affiliation":null,"fullname":"Cha, Eun-Jong","name":"Eun-Jong","pid":[],"rank":26,"surname":"Cha"},{"affiliation":null,"fullname":"Kim, Wun-Jae","name":"Wun-Jae","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8060-8926"}],"rank":27,"surname":"Kim"}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"eng","classname":"English","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Original Article"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Fundamental Science for Neurourology"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"MicroRNAs"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Neoplasms"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Herpesviridae"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Hyperplasia"}],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Purpose: Previously, we reported the presence of virus-encoded microRNAs (miRNAs) in the urine of prostate cancer (CaP) patients. In this study, we investigated the expression of two herpes virus-encoded miRNAs in prostate tissue. Methods: A total of 175 tissue samples from noncancerous benign prostatic hyperplasia (BPH), 248 tissue samples from patients with CaP and BPH, and 50 samples from noncancerous surrounding tissues from these same patients were analyzed for the expression of two herpes virus-encoded miRNAs by real-time polymerase chain reaction (PCR) and immunocytochemistry using nanoparticles as molecular beacons. Results: Real-time reverse transcription-PCR results revealed significantly higher expression of hsv1-miR-H18 and hsv2-miRH9- 5p in surrounding noncancerous and CaP tissues than that in BPH tissue (each comparison, P<0.001). Of note, these miRNA were expressed equivalently in the CaP tissues and surrounding noncancerous tissues. Moreover, immunocytochemistry clearly demonstrated a significant enrichment of both hsv1-miR-H18 and hsv2-miR-H9 beacon-labeled cells in CaP and surrounding noncancerous tissue compared to that in BPH tissue (each comparison, P<0.05 for hsv1-miR-H18 and hsv2- miR-H9). Conclusions: These results suggest that increased expression of hsv1-miR-H18 and hsv2-miR-H95p might be associated with tumorigenesis in the prostate. Further studies will be required to elucidate the role of these miRNAs with respect to CaP and herpes viral infections."}],"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"embargoenddate":null,"resourcetype":null,"context":[],"instance":[{"accessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"collectedfrom":{"dataInfo":null,"key":"10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357","value":"PubMed Central"},"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"distributionlocation":"","hostedby":{"dataInfo":null,"key":"10|opendoar____::8b6dd7db9af49e67306feb59a8bdc52c","value":"Europe PubMed Central"},"instancetype":{"classid":"0001","classname":"peerReviewed","schemeid":"dnet:publication_resource","schemename":"dnet:publication_resource"},"license":null,"processingchargeamount":null,"processingchargecurrency":null,"refereed":null,"url":["http://europepmc.org/articles/PMC4932644"]}]} +{"id":"50|doi_________::0ca46ff10b2b4c756191719d85302b14","dateofcollection":"2019-02-15","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":""},"bestaccessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:actionset","classname":"sysimport:actionset","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::081b82f96300b6a6e3d282bad31cb6e2","value":"Crossref"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::5f532a3fc4f1ea403f37070f59a7a53a","value":"Microsoft Academic Graph"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::8ac8380272269217cb09a928c8caa993","value":"UnpayWall"}],"pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"qualifier":{"classid":"doi","classname":"doi","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"10.5213/inj.1632552.276"}],"author":[{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Seok Joong Yun","name":"Seok Joong","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2105974574"}],"rank":1,"surname":"Yun"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Pildu Jeong","name":"Pildu","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2041919263"}],"rank":2,"surname":"Jeong"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ho Won Kang","name":"Ho Won","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2164408067"}],"rank":3,"surname":"Kang"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Inha University"}],"fullname":"Helen Ki Shinn","name":"Helen Ki","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2045077081"}],"rank":4,"surname":"Shinn"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ye-Hwan Kim","name":"Ye-Hwan","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2276303457"}],"rank":5,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Chunri Yan","name":"Chunri","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2186750404"}],"rank":6,"surname":"Yan"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Young-Ki Choi","name":"Young-Ki","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2311466124"}],"rank":7,"surname":"Choi"},{"affiliation":[],"fullname":"Dongho Kim","name":"Dongho","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2644843893"}],"rank":8,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Dong Hee Ryu","name":"Dong Hee","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2117604941"}],"rank":9,"surname":"Ryu"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Yun-Sok Ha","name":"Yun-Sok","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2145233282"}],"rank":10,"surname":"Ha"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Tae-Hwan Kim","name":"Tae-Hwan","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2509096378"}],"rank":11,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Tae Gyun Kwon","name":"Tae Gyun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"1978978081"}],"rank":12,"surname":"Kwon"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Daejeon University"}],"fullname":"Jung Min Kim","name":"Jung Min","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2265841962"}],"rank":13,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"KAIST"}],"fullname":"Sang Heon Suh","name":"Sang Heon","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2890693470"}],"rank":14,"surname":"Suh"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Korea Research Institute of Bioscience and Biotechnology"}],"fullname":"Seon-Kyu Kim","name":"Seon-Kyu","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2162364977"}],"rank":15,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Korea Research Institute of Bioscience and Biotechnology"}],"fullname":"Seon-Young Kim","name":"Seon-Young","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2344797375"}],"rank":16,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Seoul National University Bundang Hospital"}],"fullname":"Sang Tae Kim","name":"Sang Tae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2257827509"}],"rank":17,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Won Tae Kim","name":"Won Tae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2617237649"}],"rank":18,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ok-Jun Lee","name":"Ok-Jun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2112231548"}],"rank":19,"surname":"Lee"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chung-Ang University"}],"fullname":"Sung-Kwon Moon","name":"Sung-Kwon","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2796689429"}],"rank":20,"surname":"Moon"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Nam-Hyung Kim","name":"Nam-Hyung","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2136287741"}],"rank":21,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Rutgers University"}],"fullname":"Isaac Yi Kim","name":"Isaac Yi","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2015295992"}],"rank":22,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Harvard University"}],"fullname":"Jayoung Kim","name":"Jayoung","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2130848131"}],"rank":23,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kosin University"}],"fullname":"Hee-Jae Cha","name":"Hee-Jae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2113489867"}],"rank":24,"surname":"Cha"},{"affiliation":[],"fullname":"Yung-Hyun Choi","name":"Yung-Hyun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2151282194"}],"rank":25,"surname":"Choi"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Eun-Jong Cha","name":"Eun-Jong","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2109572239"}],"rank":26,"surname":"Cha"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Wun-Jae Kim","name":"Wun-Jae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2113339670"}],"rank":27,"surname":"Kim"}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"und","classname":"Undetermined","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Purpose:"}],"dateofacceptance":null,"embargoenddate":null,"resourcetype":null,"context":null,"instance":null} \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge3.json b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge3.json index c82c8c83e..5af2e188f 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge3.json +++ b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/publication_merge3.json @@ -1,3 +1,3 @@ -{"id":"50|doajarticles::842fa3b99fcdccafb4d5c8a815f56efa","dateofcollection":"2020-04-06T12:22:31.216Z","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Korean Continence Society"},"bestaccessright":null,"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":null,"key":"10|driver______::bee53aa31dc2cbb538c10c2b65fa5824","value":"DOAJ-Articles"}],"pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"doi","classname":"doi","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"10.5213/inj.1632552.276"}, {"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"doi","classname":"doi","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"10.5213/inj.1632552.27XXXXX"}],"author":[{"affiliation":null,"fullname":"Seok Joong Yun","name":null,"pid":[],"rank":1,"surname":null},{"affiliation":null,"fullname":"Pildu Jeong","name":null,"pid":[],"rank":2,"surname":null},{"affiliation":null,"fullname":"Ho Won Kang","name":null,"pid":[],"rank":3,"surname":null},{"affiliation":null,"fullname":"Helen Ki Shinn","name":null,"pid":[],"rank":4,"surname":null},{"affiliation":null,"fullname":"Ye-Hwan Kim","name":null,"pid":[],"rank":5,"surname":null},{"affiliation":null,"fullname":"Chunri Yan","name":null,"pid":[],"rank":6,"surname":null},{"affiliation":null,"fullname":"Young-Ki Choi","name":null,"pid":[],"rank":7,"surname":null},{"affiliation":null,"fullname":"Dongho Kim","name":null,"pid":[],"rank":8,"surname":null},{"affiliation":null,"fullname":"Dong Hee Ryu","name":null,"pid":[],"rank":9,"surname":null},{"affiliation":null,"fullname":"Yun-Sok Ha","name":null,"pid":[],"rank":10,"surname":null},{"affiliation":null,"fullname":"Tae-Hwan Kim","name":null,"pid":[],"rank":11,"surname":null},{"affiliation":null,"fullname":"Tae Gyun Kwon","name":null,"pid":[],"rank":12,"surname":null},{"affiliation":null,"fullname":"Jung Min Kim","name":null,"pid":[],"rank":13,"surname":null},{"affiliation":null,"fullname":"Sang Heon Suh","name":null,"pid":[],"rank":14,"surname":null},{"affiliation":null,"fullname":"Seon-Kyu Kim","name":null,"pid":[],"rank":15,"surname":null},{"affiliation":null,"fullname":"Seon-Young Kim","name":null,"pid":[],"rank":16,"surname":null},{"affiliation":null,"fullname":"Sang Tae Kim","name":null,"pid":[],"rank":17,"surname":null},{"affiliation":null,"fullname":"Won Tae Kim","name":null,"pid":[],"rank":18,"surname":null},{"affiliation":null,"fullname":"Ok-Jun Lee","name":null,"pid":[],"rank":19,"surname":null},{"affiliation":null,"fullname":"Sung-Kwon Moon","name":null,"pid":[],"rank":20,"surname":null},{"affiliation":null,"fullname":"Nam-Hyung Kim","name":null,"pid":[],"rank":21,"surname":null},{"affiliation":null,"fullname":"Isaac Yi Kim","name":null,"pid":[],"rank":22,"surname":null},{"affiliation":null,"fullname":"Jayoung Kim","name":null,"pid":[],"rank":23,"surname":null},{"affiliation":null,"fullname":"Hee-Jae Cha","name":null,"pid":[],"rank":24,"surname":null},{"affiliation":null,"fullname":"Yung-Hyun Choi","name":null,"pid":[],"rank":25,"surname":null},{"affiliation":null,"fullname":"Eun-Jong Cha","name":null,"pid":[],"rank":26,"surname":null},{"affiliation":null,"fullname":"Wun-Jae Kim","name":null,"pid":[],"rank":27,"surname":null}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"eng","classname":"English","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"MicroRNAs"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Neoplasms"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Herpesviridae"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Hyperplasia"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Diseases of the genitourinary system. Urology"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"RC870-923"}],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Purpose: Previously, we reported the presence of virus-encoded microRNAs (miRNAs) in the urine of prostate cancer (CaP) patients. In this study, we investigated the expression of two herpes virus-encoded miRNAs in prostate tissue. Methods: A total of 175 tissue samples from noncancerous benign prostatic hyperplasia (BPH), 248 tissue samples from patients with CaP and BPH, and 50 samples from noncancerous surrounding tissues from these same patients were analyzed for the expression of two herpes virus-encoded miRNAs by real-time polymerase chain reaction (PCR) and immunocytochemistry using nanoparticles as molecular beacons. Results: Real-time reverse transcription-PCR results revealed significantly higher expression of hsv1-miR-H18 and hsv2-miRH9- 5p in surrounding noncancerous and CaP tissues than that in BPH tissue (each comparison, P<0.001). Of note, these miRNA were expressed equivalently in the CaP tissues and surrounding noncancerous tissues. Moreover, immunocytochemistry clearly demonstrated a significant enrichment of both hsv1-miR-H18 and hsv2-miR-H9 beacon-labeled cells in CaP and surrounding noncancerous tissue compared to that in BPH tissue (each comparison, P<0.05 for hsv1-miR-H18 and hsv2- miR-H9). Conclusions: These results suggest that increased expression of hsv1-miR-H18 and hsv2-miR-H95p might be associated with tumorigenesis in the prostate. Further studies will be required to elucidate the role of these miRNAs with respect to CaP and herpes viral infections."}],"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"embargoenddate":null,"resourcetype":null,"context":[],"instance":[{"accessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"collectedfrom":{"dataInfo":null,"key":"10|driver______::bee53aa31dc2cbb538c10c2b65fa5824","value":"DOAJ-Articles"},"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"distributionlocation":"","hostedby":{"dataInfo":null,"key":"10|doajarticles::52db9a4f8e176f6e8e1d9f0c1e0a2de0","value":"International Neurourology Journal"},"instancetype":{"classid":"0001","classname":"peerReviewed","schemeid":"dnet:publication_resource","schemename":"dnet:publication_resource"},"license":null,"processingchargeamount":null,"processingchargecurrency":null,"refereed":null,"url":["http://www.einj.org/upload/pdf/inj-1632552-276.pdf","https://doaj.org/toc/2093-4777","https://doaj.org/toc/2093-6931"]}]} -{"id":"50|od_______267::b5f5da11a8239ef57655cea8675cb466","dateofcollection":"","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Korean Continence Society"},"bestaccessright":null,"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":null,"key":"10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357","value":"PubMed Central"}],"pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"doi","classname":"doi","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"10.5213/inj.1632552.276"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"pmc","classname":"pmc","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"PMC4932644"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"pmid","classname":"pmid","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"27377944"}],"author":[{"affiliation":null,"fullname":"Yun, Seok Joong","name":"Seok Joong","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-7737-4746"}],"rank":1,"surname":"Yun"},{"affiliation":null,"fullname":"Jeong, Pildu","name":"Pildu","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-5602-5376"}],"rank":2,"surname":"Jeong"},{"affiliation":null,"fullname":"Kang, Ho Won","name":"Ho Won","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8164-4427"}],"rank":3,"surname":"Kang"},{"affiliation":null,"fullname":"Shinn, Helen Ki","name":"Helen Ki","pid":[],"rank":4,"surname":"Shinn"},{"affiliation":null,"fullname":"Kim, Ye-Hwan","name":"Ye-Hwan","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8676-7119"}],"rank":5,"surname":"Kim"},{"affiliation":null,"fullname":"Yan, Chunri","name":"Chunri","pid":[],"rank":6,"surname":"Yan"},{"affiliation":null,"fullname":"Choi, Young-Ki","name":"Young-Ki","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1894-9869"}],"rank":7,"surname":"Choi"},{"affiliation":null,"fullname":"Kim, Dongho","name":"Dongho","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1409-3311"}],"rank":8,"surname":"Kim"},{"affiliation":null,"fullname":"Ryu, Dong Hee","name":"Dong Hee","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-6088-298X"}],"rank":9,"surname":"Ryu"},{"affiliation":null,"fullname":"Ha, Yun-Sok","name":"Yun-Sok","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0003-3732-9814"}],"rank":10,"surname":"Ha"},{"affiliation":null,"fullname":"Kim, Tae-Hwan","name":"Tae-Hwan","pid":[],"rank":11,"surname":"Kim"},{"affiliation":null,"fullname":"Kwon, Tae Gyun","name":"Tae Gyun","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-4390-0952"}],"rank":12,"surname":"Kwon"},{"affiliation":null,"fullname":"Kim, Jung Min","name":"Jung Min","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-6319-0217"}],"rank":13,"surname":"Kim"},{"affiliation":null,"fullname":"Suh, Sang Heon","name":"Sang Heon","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0003-4560-8880"}],"rank":14,"surname":"Suh"},{"affiliation":null,"fullname":"Kim, Seon-Kyu","name":"Seon-Kyu","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-4176-5187"}],"rank":15,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Seon-Young","name":"Seon-Young","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1030-7730"}],"rank":16,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Sang Tae","name":"Sang Tae","pid":[],"rank":17,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Won Tae","name":"Won Tae","pid":[],"rank":18,"surname":"Kim"},{"affiliation":null,"fullname":"Lee, Ok-Jun","name":"Ok-Jun","pid":[],"rank":19,"surname":"Lee"},{"affiliation":null,"fullname":"Moon, Sung-Kwon","name":"Sung-Kwon","pid":[],"rank":20,"surname":"Moon"},{"affiliation":null,"fullname":"Kim, Nam-Hyung","name":"Nam-Hyung","pid":[],"rank":21,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Isaac Yi","name":"Isaac Yi","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1967-5281"}],"rank":22,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Jayoung","name":"Jayoung","pid":[],"rank":23,"surname":"Kim"},{"affiliation":null,"fullname":"Cha, Hee-Jae","name":"Hee-Jae","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-6963-2685"}],"rank":24,"surname":"Cha"},{"affiliation":null,"fullname":"Choi, Yung-Hyun","name":"Yung-Hyun","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1454-3124"}],"rank":25,"surname":"Choi"},{"affiliation":null,"fullname":"Cha, Eun-Jong","name":"Eun-Jong","pid":[],"rank":26,"surname":"Cha"},{"affiliation":null,"fullname":"Kim, Wun-Jae","name":"Wun-Jae","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8060-8926"}],"rank":27,"surname":"Kim"}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"eng","classname":"English","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Original Article"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Fundamental Science for Neurourology"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"MicroRNAs"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Neoplasms"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Herpesviridae"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Hyperplasia"}],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Purpose: Previously, we reported the presence of virus-encoded microRNAs (miRNAs) in the urine of prostate cancer (CaP) patients. In this study, we investigated the expression of two herpes virus-encoded miRNAs in prostate tissue. Methods: A total of 175 tissue samples from noncancerous benign prostatic hyperplasia (BPH), 248 tissue samples from patients with CaP and BPH, and 50 samples from noncancerous surrounding tissues from these same patients were analyzed for the expression of two herpes virus-encoded miRNAs by real-time polymerase chain reaction (PCR) and immunocytochemistry using nanoparticles as molecular beacons. Results: Real-time reverse transcription-PCR results revealed significantly higher expression of hsv1-miR-H18 and hsv2-miRH9- 5p in surrounding noncancerous and CaP tissues than that in BPH tissue (each comparison, P<0.001). Of note, these miRNA were expressed equivalently in the CaP tissues and surrounding noncancerous tissues. Moreover, immunocytochemistry clearly demonstrated a significant enrichment of both hsv1-miR-H18 and hsv2-miR-H9 beacon-labeled cells in CaP and surrounding noncancerous tissue compared to that in BPH tissue (each comparison, P<0.05 for hsv1-miR-H18 and hsv2- miR-H9). Conclusions: These results suggest that increased expression of hsv1-miR-H18 and hsv2-miR-H95p might be associated with tumorigenesis in the prostate. Further studies will be required to elucidate the role of these miRNAs with respect to CaP and herpes viral infections."}],"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"embargoenddate":null,"resourcetype":null,"context":[],"instance":[{"accessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"collectedfrom":{"dataInfo":null,"key":"10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357","value":"PubMed Central"},"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"distributionlocation":"","hostedby":{"dataInfo":null,"key":"10|opendoar____::8b6dd7db9af49e67306feb59a8bdc52c","value":"Europe PubMed Central"},"instancetype":{"classid":"0001","classname":"peerReviewed","schemeid":"dnet:publication_resource","schemename":"dnet:publication_resource"},"license":null,"processingchargeamount":null,"processingchargecurrency":null,"refereed":null,"url":["http://europepmc.org/articles/PMC4932644"]}]} -{"id":"50|doiboost____::0ca46ff10b2b4c756191719d85302b14","dateofcollection":"2019-02-15","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":""},"bestaccessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:actionset","classname":"sysimport:actionset","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::081b82f96300b6a6e3d282bad31cb6e2","value":"Crossref"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::5f532a3fc4f1ea403f37070f59a7a53a","value":"Microsoft Academic Graph"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::8ac8380272269217cb09a928c8caa993","value":"UnpayWall"}],"pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"qualifier":{"classid":"doi","classname":"doi","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"10.5213/inj.1632552.276"}],"author":[{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Seok Joong Yun","name":"Seok Joong","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2105974574"}],"rank":1,"surname":"Yun"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Pildu Jeong","name":"Pildu","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2041919263"}],"rank":2,"surname":"Jeong"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ho Won Kang","name":"Ho Won","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2164408067"}],"rank":3,"surname":"Kang"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Inha University"}],"fullname":"Helen Ki Shinn","name":"Helen Ki","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2045077081"}],"rank":4,"surname":"Shinn"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ye-Hwan Kim","name":"Ye-Hwan","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2276303457"}],"rank":5,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Chunri Yan","name":"Chunri","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2186750404"}],"rank":6,"surname":"Yan"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Young-Ki Choi","name":"Young-Ki","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2311466124"}],"rank":7,"surname":"Choi"},{"affiliation":[],"fullname":"Dongho Kim","name":"Dongho","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2644843893"}],"rank":8,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Dong Hee Ryu","name":"Dong Hee","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2117604941"}],"rank":9,"surname":"Ryu"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Yun-Sok Ha","name":"Yun-Sok","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2145233282"}],"rank":10,"surname":"Ha"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Tae-Hwan Kim","name":"Tae-Hwan","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2509096378"}],"rank":11,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Tae Gyun Kwon","name":"Tae Gyun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"1978978081"}],"rank":12,"surname":"Kwon"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Daejeon University"}],"fullname":"Jung Min Kim","name":"Jung Min","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2265841962"}],"rank":13,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"KAIST"}],"fullname":"Sang Heon Suh","name":"Sang Heon","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2890693470"}],"rank":14,"surname":"Suh"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Korea Research Institute of Bioscience and Biotechnology"}],"fullname":"Seon-Kyu Kim","name":"Seon-Kyu","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2162364977"}],"rank":15,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Korea Research Institute of Bioscience and Biotechnology"}],"fullname":"Seon-Young Kim","name":"Seon-Young","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2344797375"}],"rank":16,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Seoul National University Bundang Hospital"}],"fullname":"Sang Tae Kim","name":"Sang Tae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2257827509"}],"rank":17,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Won Tae Kim","name":"Won Tae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2617237649"}],"rank":18,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ok-Jun Lee","name":"Ok-Jun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2112231548"}],"rank":19,"surname":"Lee"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chung-Ang University"}],"fullname":"Sung-Kwon Moon","name":"Sung-Kwon","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2796689429"}],"rank":20,"surname":"Moon"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Nam-Hyung Kim","name":"Nam-Hyung","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2136287741"}],"rank":21,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Rutgers University"}],"fullname":"Isaac Yi Kim","name":"Isaac Yi","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2015295992"}],"rank":22,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Harvard University"}],"fullname":"Jayoung Kim","name":"Jayoung","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2130848131"}],"rank":23,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kosin University"}],"fullname":"Hee-Jae Cha","name":"Hee-Jae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2113489867"}],"rank":24,"surname":"Cha"},{"affiliation":[],"fullname":"Yung-Hyun Choi","name":"Yung-Hyun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2151282194"}],"rank":25,"surname":"Choi"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Eun-Jong Cha","name":"Eun-Jong","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2109572239"}],"rank":26,"surname":"Cha"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Wun-Jae Kim","name":"Wun-Jae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2113339670"}],"rank":27,"surname":"Kim"}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"und","classname":"Undetermined","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Purpose:"}],"dateofacceptance":null,"embargoenddate":null,"resourcetype":null,"context":null,"instance":null} \ No newline at end of file +{"id":"50|doi_________::842fa3b99fcdccafb4d5c8a815f56efa","dateofcollection":"2020-04-06T12:22:31.216Z","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Korean Continence Society"},"bestaccessright":null,"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":null,"key":"10|driver______::bee53aa31dc2cbb538c10c2b65fa5824","value":"DOAJ-Articles"}],"pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"doi","classname":"doi","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"10.5213/inj.1632552.276"}, {"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"doi","classname":"doi","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"10.5213/inj.1632552.27XXXXX"}],"author":[{"affiliation":null,"fullname":"Seok Joong Yun","name":null,"pid":[],"rank":1,"surname":null},{"affiliation":null,"fullname":"Pildu Jeong","name":null,"pid":[],"rank":2,"surname":null},{"affiliation":null,"fullname":"Ho Won Kang","name":null,"pid":[],"rank":3,"surname":null},{"affiliation":null,"fullname":"Helen Ki Shinn","name":null,"pid":[],"rank":4,"surname":null},{"affiliation":null,"fullname":"Ye-Hwan Kim","name":null,"pid":[],"rank":5,"surname":null},{"affiliation":null,"fullname":"Chunri Yan","name":null,"pid":[],"rank":6,"surname":null},{"affiliation":null,"fullname":"Young-Ki Choi","name":null,"pid":[],"rank":7,"surname":null},{"affiliation":null,"fullname":"Dongho Kim","name":null,"pid":[],"rank":8,"surname":null},{"affiliation":null,"fullname":"Dong Hee Ryu","name":null,"pid":[],"rank":9,"surname":null},{"affiliation":null,"fullname":"Yun-Sok Ha","name":null,"pid":[],"rank":10,"surname":null},{"affiliation":null,"fullname":"Tae-Hwan Kim","name":null,"pid":[],"rank":11,"surname":null},{"affiliation":null,"fullname":"Tae Gyun Kwon","name":null,"pid":[],"rank":12,"surname":null},{"affiliation":null,"fullname":"Jung Min Kim","name":null,"pid":[],"rank":13,"surname":null},{"affiliation":null,"fullname":"Sang Heon Suh","name":null,"pid":[],"rank":14,"surname":null},{"affiliation":null,"fullname":"Seon-Kyu Kim","name":null,"pid":[],"rank":15,"surname":null},{"affiliation":null,"fullname":"Seon-Young Kim","name":null,"pid":[],"rank":16,"surname":null},{"affiliation":null,"fullname":"Sang Tae Kim","name":null,"pid":[],"rank":17,"surname":null},{"affiliation":null,"fullname":"Won Tae Kim","name":null,"pid":[],"rank":18,"surname":null},{"affiliation":null,"fullname":"Ok-Jun Lee","name":null,"pid":[],"rank":19,"surname":null},{"affiliation":null,"fullname":"Sung-Kwon Moon","name":null,"pid":[],"rank":20,"surname":null},{"affiliation":null,"fullname":"Nam-Hyung Kim","name":null,"pid":[],"rank":21,"surname":null},{"affiliation":null,"fullname":"Isaac Yi Kim","name":null,"pid":[],"rank":22,"surname":null},{"affiliation":null,"fullname":"Jayoung Kim","name":null,"pid":[],"rank":23,"surname":null},{"affiliation":null,"fullname":"Hee-Jae Cha","name":null,"pid":[],"rank":24,"surname":null},{"affiliation":null,"fullname":"Yung-Hyun Choi","name":null,"pid":[],"rank":25,"surname":null},{"affiliation":null,"fullname":"Eun-Jong Cha","name":null,"pid":[],"rank":26,"surname":null},{"affiliation":null,"fullname":"Wun-Jae Kim","name":null,"pid":[],"rank":27,"surname":null}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"eng","classname":"English","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"MicroRNAs"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Neoplasms"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Herpesviridae"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Hyperplasia"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Diseases of the genitourinary system. Urology"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"RC870-923"}],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Purpose: Previously, we reported the presence of virus-encoded microRNAs (miRNAs) in the urine of prostate cancer (CaP) patients. In this study, we investigated the expression of two herpes virus-encoded miRNAs in prostate tissue. Methods: A total of 175 tissue samples from noncancerous benign prostatic hyperplasia (BPH), 248 tissue samples from patients with CaP and BPH, and 50 samples from noncancerous surrounding tissues from these same patients were analyzed for the expression of two herpes virus-encoded miRNAs by real-time polymerase chain reaction (PCR) and immunocytochemistry using nanoparticles as molecular beacons. Results: Real-time reverse transcription-PCR results revealed significantly higher expression of hsv1-miR-H18 and hsv2-miRH9- 5p in surrounding noncancerous and CaP tissues than that in BPH tissue (each comparison, P<0.001). Of note, these miRNA were expressed equivalently in the CaP tissues and surrounding noncancerous tissues. Moreover, immunocytochemistry clearly demonstrated a significant enrichment of both hsv1-miR-H18 and hsv2-miR-H9 beacon-labeled cells in CaP and surrounding noncancerous tissue compared to that in BPH tissue (each comparison, P<0.05 for hsv1-miR-H18 and hsv2- miR-H9). Conclusions: These results suggest that increased expression of hsv1-miR-H18 and hsv2-miR-H95p might be associated with tumorigenesis in the prostate. Further studies will be required to elucidate the role of these miRNAs with respect to CaP and herpes viral infections."}],"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"embargoenddate":null,"resourcetype":null,"context":[],"instance":[{"accessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"collectedfrom":{"dataInfo":null,"key":"10|driver______::bee53aa31dc2cbb538c10c2b65fa5824","value":"DOAJ-Articles"},"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk:repository","classname":"sysimport:crosswalk:repository","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"distributionlocation":"","hostedby":{"dataInfo":null,"key":"10|doajarticles::52db9a4f8e176f6e8e1d9f0c1e0a2de0","value":"International Neurourology Journal"},"instancetype":{"classid":"0001","classname":"peerReviewed","schemeid":"dnet:publication_resource","schemename":"dnet:publication_resource"},"license":null,"processingchargeamount":null,"processingchargecurrency":null,"refereed":null,"url":["http://www.einj.org/upload/pdf/inj-1632552-276.pdf","https://doaj.org/toc/2093-4777","https://doaj.org/toc/2093-6931"]}]} +{"id":"50|doi_________::b5f5da11a8239ef57655cea8675cb466","dateofcollection":"","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Korean Continence Society"},"bestaccessright":null,"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":null,"key":"10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357","value":"PubMed Central"}],"pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"doi","classname":"doi","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"10.5213/inj.1632552.276"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"pmc","classname":"pmc","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"PMC4932644"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"pmid","classname":"pmid","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"27377944"}],"author":[{"affiliation":null,"fullname":"Yun, Seok Joong","name":"Seok Joong","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-7737-4746"}],"rank":1,"surname":"Yun"},{"affiliation":null,"fullname":"Jeong, Pildu","name":"Pildu","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-5602-5376"}],"rank":2,"surname":"Jeong"},{"affiliation":null,"fullname":"Kang, Ho Won","name":"Ho Won","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8164-4427"}],"rank":3,"surname":"Kang"},{"affiliation":null,"fullname":"Shinn, Helen Ki","name":"Helen Ki","pid":[],"rank":4,"surname":"Shinn"},{"affiliation":null,"fullname":"Kim, Ye-Hwan","name":"Ye-Hwan","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8676-7119"}],"rank":5,"surname":"Kim"},{"affiliation":null,"fullname":"Yan, Chunri","name":"Chunri","pid":[],"rank":6,"surname":"Yan"},{"affiliation":null,"fullname":"Choi, Young-Ki","name":"Young-Ki","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1894-9869"}],"rank":7,"surname":"Choi"},{"affiliation":null,"fullname":"Kim, Dongho","name":"Dongho","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1409-3311"}],"rank":8,"surname":"Kim"},{"affiliation":null,"fullname":"Ryu, Dong Hee","name":"Dong Hee","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-6088-298X"}],"rank":9,"surname":"Ryu"},{"affiliation":null,"fullname":"Ha, Yun-Sok","name":"Yun-Sok","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0003-3732-9814"}],"rank":10,"surname":"Ha"},{"affiliation":null,"fullname":"Kim, Tae-Hwan","name":"Tae-Hwan","pid":[],"rank":11,"surname":"Kim"},{"affiliation":null,"fullname":"Kwon, Tae Gyun","name":"Tae Gyun","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-4390-0952"}],"rank":12,"surname":"Kwon"},{"affiliation":null,"fullname":"Kim, Jung Min","name":"Jung Min","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0001-6319-0217"}],"rank":13,"surname":"Kim"},{"affiliation":null,"fullname":"Suh, Sang Heon","name":"Sang Heon","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0003-4560-8880"}],"rank":14,"surname":"Suh"},{"affiliation":null,"fullname":"Kim, Seon-Kyu","name":"Seon-Kyu","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-4176-5187"}],"rank":15,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Seon-Young","name":"Seon-Young","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1030-7730"}],"rank":16,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Sang Tae","name":"Sang Tae","pid":[],"rank":17,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Won Tae","name":"Won Tae","pid":[],"rank":18,"surname":"Kim"},{"affiliation":null,"fullname":"Lee, Ok-Jun","name":"Ok-Jun","pid":[],"rank":19,"surname":"Lee"},{"affiliation":null,"fullname":"Moon, Sung-Kwon","name":"Sung-Kwon","pid":[],"rank":20,"surname":"Moon"},{"affiliation":null,"fullname":"Kim, Nam-Hyung","name":"Nam-Hyung","pid":[],"rank":21,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Isaac Yi","name":"Isaac Yi","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1967-5281"}],"rank":22,"surname":"Kim"},{"affiliation":null,"fullname":"Kim, Jayoung","name":"Jayoung","pid":[],"rank":23,"surname":"Kim"},{"affiliation":null,"fullname":"Cha, Hee-Jae","name":"Hee-Jae","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-6963-2685"}],"rank":24,"surname":"Cha"},{"affiliation":null,"fullname":"Choi, Yung-Hyun","name":"Yung-Hyun","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-1454-3124"}],"rank":25,"surname":"Choi"},{"affiliation":null,"fullname":"Cha, Eun-Jong","name":"Eun-Jong","pid":[],"rank":26,"surname":"Cha"},{"affiliation":null,"fullname":"Kim, Wun-Jae","name":"Wun-Jae","pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"ORCID","classname":"ORCID","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"0000-0002-8060-8926"}],"rank":27,"surname":"Kim"}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"eng","classname":"English","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Original Article"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Fundamental Science for Neurourology"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"MicroRNAs"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Neoplasms"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Herpesviridae"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"qualifier":{"classid":"","classname":"","schemeid":"","schemename":""},"value":"Prostate Hyperplasia"}],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"Purpose: Previously, we reported the presence of virus-encoded microRNAs (miRNAs) in the urine of prostate cancer (CaP) patients. In this study, we investigated the expression of two herpes virus-encoded miRNAs in prostate tissue. Methods: A total of 175 tissue samples from noncancerous benign prostatic hyperplasia (BPH), 248 tissue samples from patients with CaP and BPH, and 50 samples from noncancerous surrounding tissues from these same patients were analyzed for the expression of two herpes virus-encoded miRNAs by real-time polymerase chain reaction (PCR) and immunocytochemistry using nanoparticles as molecular beacons. Results: Real-time reverse transcription-PCR results revealed significantly higher expression of hsv1-miR-H18 and hsv2-miRH9- 5p in surrounding noncancerous and CaP tissues than that in BPH tissue (each comparison, P<0.001). Of note, these miRNA were expressed equivalently in the CaP tissues and surrounding noncancerous tissues. Moreover, immunocytochemistry clearly demonstrated a significant enrichment of both hsv1-miR-H18 and hsv2-miR-H9 beacon-labeled cells in CaP and surrounding noncancerous tissue compared to that in BPH tissue (each comparison, P<0.05 for hsv1-miR-H18 and hsv2- miR-H9). Conclusions: These results suggest that increased expression of hsv1-miR-H18 and hsv2-miR-H95p might be associated with tumorigenesis in the prostate. Further studies will be required to elucidate the role of these miRNAs with respect to CaP and herpes viral infections."}],"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"embargoenddate":null,"resourcetype":null,"context":[],"instance":[{"accessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"collectedfrom":{"dataInfo":null,"key":"10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357","value":"PubMed Central"},"dateofacceptance":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:crosswalk","classname":"sysimport:crosswalk","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"value":"2016-06-01"},"distributionlocation":"","hostedby":{"dataInfo":null,"key":"10|opendoar____::8b6dd7db9af49e67306feb59a8bdc52c","value":"Europe PubMed Central"},"instancetype":{"classid":"0001","classname":"peerReviewed","schemeid":"dnet:publication_resource","schemename":"dnet:publication_resource"},"license":null,"processingchargeamount":null,"processingchargecurrency":null,"refereed":null,"url":["http://europepmc.org/articles/PMC4932644"]}]} +{"id":"50|doi_________::0ca46ff10b2b4c756191719d85302b14","dateofcollection":"2019-02-15","title":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"qualifier":{"classid":"main title","classname":"main title","schemeid":"dnet:dataCite_title","schemename":"dnet:dataCite_title"},"value":"Increased Expression of Herpes Virus-Encoded hsv1-miR-H18 and hsv2-miR-H9-5p in Cancer-Containing Prostate Tissue Compared to That in Benign Prostate Hyperplasia Tissue"}],"publisher":{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":""},"bestaccessright":{"classid":"OPEN","classname":"Open Access","schemeid":"dnet:access_modes","schemename":"dnet:access_modes"},"dataInfo":{"deletedbyinference":true,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"sysimport:actionset","classname":"sysimport:actionset","schemeid":"dnet:provenanceActions","schemename":"dnet:provenanceActions"},"trust":"0.9"},"collectedfrom":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::081b82f96300b6a6e3d282bad31cb6e2","value":"Crossref"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::5f532a3fc4f1ea403f37070f59a7a53a","value":"Microsoft Academic Graph"},{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"key":"10|openaire____::8ac8380272269217cb09a928c8caa993","value":"UnpayWall"}],"pid":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"qualifier":{"classid":"doi","classname":"doi","schemeid":"dnet:pid_types","schemename":"dnet:pid_types"},"value":"10.5213/inj.1632552.276"}],"author":[{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Seok Joong Yun","name":"Seok Joong","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2105974574"}],"rank":1,"surname":"Yun"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Pildu Jeong","name":"Pildu","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2041919263"}],"rank":2,"surname":"Jeong"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ho Won Kang","name":"Ho Won","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2164408067"}],"rank":3,"surname":"Kang"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Inha University"}],"fullname":"Helen Ki Shinn","name":"Helen Ki","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2045077081"}],"rank":4,"surname":"Shinn"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ye-Hwan Kim","name":"Ye-Hwan","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2276303457"}],"rank":5,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Chunri Yan","name":"Chunri","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2186750404"}],"rank":6,"surname":"Yan"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Young-Ki Choi","name":"Young-Ki","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2311466124"}],"rank":7,"surname":"Choi"},{"affiliation":[],"fullname":"Dongho Kim","name":"Dongho","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2644843893"}],"rank":8,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Dong Hee Ryu","name":"Dong Hee","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2117604941"}],"rank":9,"surname":"Ryu"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Yun-Sok Ha","name":"Yun-Sok","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2145233282"}],"rank":10,"surname":"Ha"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Tae-Hwan Kim","name":"Tae-Hwan","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2509096378"}],"rank":11,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kyungpook National University"}],"fullname":"Tae Gyun Kwon","name":"Tae Gyun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"1978978081"}],"rank":12,"surname":"Kwon"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Daejeon University"}],"fullname":"Jung Min Kim","name":"Jung Min","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2265841962"}],"rank":13,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"KAIST"}],"fullname":"Sang Heon Suh","name":"Sang Heon","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2890693470"}],"rank":14,"surname":"Suh"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Korea Research Institute of Bioscience and Biotechnology"}],"fullname":"Seon-Kyu Kim","name":"Seon-Kyu","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2162364977"}],"rank":15,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Korea Research Institute of Bioscience and Biotechnology"}],"fullname":"Seon-Young Kim","name":"Seon-Young","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2344797375"}],"rank":16,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Seoul National University Bundang Hospital"}],"fullname":"Sang Tae Kim","name":"Sang Tae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2257827509"}],"rank":17,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Won Tae Kim","name":"Won Tae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2617237649"}],"rank":18,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Ok-Jun Lee","name":"Ok-Jun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2112231548"}],"rank":19,"surname":"Lee"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chung-Ang University"}],"fullname":"Sung-Kwon Moon","name":"Sung-Kwon","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2796689429"}],"rank":20,"surname":"Moon"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Nam-Hyung Kim","name":"Nam-Hyung","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2136287741"}],"rank":21,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Rutgers University"}],"fullname":"Isaac Yi Kim","name":"Isaac Yi","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2015295992"}],"rank":22,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Harvard University"}],"fullname":"Jayoung Kim","name":"Jayoung","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2130848131"}],"rank":23,"surname":"Kim"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Kosin University"}],"fullname":"Hee-Jae Cha","name":"Hee-Jae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2113489867"}],"rank":24,"surname":"Cha"},{"affiliation":[],"fullname":"Yung-Hyun Choi","name":"Yung-Hyun","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2151282194"}],"rank":25,"surname":"Choi"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Eun-Jong Cha","name":"Eun-Jong","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2109572239"}],"rank":26,"surname":"Cha"},{"affiliation":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Chungbuk National University"}],"fullname":"Wun-Jae Kim","name":"Wun-Jae","pid":[{"dataInfo":null,"qualifier":{"classid":"MAG Identifier","classname":"MAG Identifier","schemeid":null,"schemename":null},"value":"2113339670"}],"rank":27,"surname":"Kim"}],"resulttype":{"classid":"publication","classname":"publication","schemeid":"dnet:result_typologies","schemename":"dnet:result_typologies"},"language":{"classid":"und","classname":"Undetermined","schemeid":"dnet:languages","schemename":"dnet:languages"},"country":[],"subject":[],"description":[{"dataInfo":{"deletedbyinference":false,"inferenceprovenance":"","inferred":false,"invisible":false,"provenanceaction":{"classid":"","classname":"","schemeid":"","schemename":""},"trust":""},"value":"Purpose:"}],"dateofacceptance":null,"embargoenddate":null,"resourcetype":null,"context":null,"instance":null} \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/software_merge.json b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/software_merge.json index b146d6102..41bab1835 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/software_merge.json +++ b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/software_merge.json @@ -1,3 +1,3 @@ -{"context": [], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:datasetarchive", "classname": "sysimport:crosswalk:datasetarchive", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "dedup-similarity-result-levenstein", "invisible": false, "trust": "0.95"}, "resourcetype": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "pid": [], "contributor": [], "resulttype": {"classid": "software", "classname": "software", "schemename": "dnet:result_typologies", "schemeid": "dnet:result_typologies"}, "relevantdate": [], "collectedfrom": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Journal.fi", "key": "10|openaire____::6eef8049d0feedc089ee009abca55e35"}], "id": "50|a89337edbe55::4930db9e954866d70916cbfba9f81f97", "subject": [], "instance": [{"refereed": null, "hostedby": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Journal.fi", "key": "10|openaire____::6eef8049d0feedc089ee009abca55e35"}, "processingchargeamount": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "license": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "url": [], "distributionlocation": "", "processingchargecurrency": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "collectedfrom": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Journal.fi", "key": "10|openaire____::6eef8049d0feedc089ee009abca55e35"}, "accessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "instancetype": {"classid": "0001", "classname": "Article", "schemename": "dnet:dataCite_resource", "schemeid": "dnet:dataCite_resource"}}], "embargoenddate": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "lastupdatetimestamp": 0, "author": [{"surname": "Go\\u0308tz", "name": "Julia", "pid": [], "rank": 5, "affiliation": [], "fullname": "G\\u00f6tz, Julia"}, {"surname": "Wolff", "name": "Stephan", "pid": [], "rank": 6, "affiliation": [], "fullname": "Wolff, Stephan"}, {"surname": "Jansen", "name": "Olav", "pid": [], "rank": 7, "affiliation": [], "fullname": "Jansen, Olav"}, {"surname": "Dressler", "name": "Dirk", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0000-0656-9999"},{"qualifier": {"classid": "id", "classname": "id"}, "value": "987654321"}], "rank": 8, "affiliation": [], "fullname": "Dressler, Dirk"}, {"surname": "Schneider", "name": "Susanne A.", "pid": [], "rank": 9, "affiliation": [], "fullname": "Schneider, Susanne A."}], "source": [], "dateofcollection": "2019-11-05T14:49:22.351Z", "fulltext": [], "dateoftransformation": "2019-11-05T16:10:58.988Z", "description": [], "format": [], "coverage": [], "publisher": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "language": {"classid": "eng", "classname": "English", "schemename": "dnet:languages", "schemeid": "dnet:languages"}, "bestaccessright": {"classid": "OPEN SOURCE", "classname": "Open Source", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "country": [], "extraInfo": [], "originalId": [], "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2018-09-30"}, "title": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "main title", "classname": "main title", "schemename": "dnet:dataCite_title", "schemeid": "dnet:dataCite_title"}, "value": "Altered brain activation in a reversal learning task unmasks adaptive changes in cognitive control in writer's cramp"}]} -{"context": [], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:repository", "classname": "sysimport:crosswalk:repository", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": true, "inferenceprovenance": "dedup-similarity-result-levenstein", "invisible": false, "trust": "0.9"}, "resourcetype": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "pid": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "doi", "classname": "doi", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "10.1016/j.nicl.2015.11.006"}], "contributor": [], "resulttype": {"classid": "software", "classname": "software", "schemename": "dnet:result_typologies", "schemeid": "dnet:result_typologies"}, "relevantdate": [], "collectedfrom": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "BASE (Open Access Aggregator)", "key": "10|openaire____::df45502607927471ecf8a6ae83683ff5"}], "id": "50|base_oa_____::0968af610a356656706657e4f234b340", "subject": [], "instance": [{"refereed": null, "hostedby": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "NeuroImage: Clinical", "key": "10|doajarticles::0c0e74daa5d95504eade9c81ebbd5b8a"}, "processingchargeamount": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "license": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "http://creativecommons.org/licenses/by-nc-nd/4.0/"}, "url": ["http://dx.doi.org/10.1016/j.nicl.2015.11.006"], "distributionlocation": "", "processingchargecurrency": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "collectedfrom": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "BASE (Open Access Aggregator)", "key": "10|openaire____::df45502607927471ecf8a6ae83683ff5"}, "accessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "instancetype": {"classid": "0001", "classname": "Article", "schemename": "dnet:publication_resource", "schemeid": "dnet:publication_resource"}}], "embargoenddate": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "lastupdatetimestamp": 0, "author": [{"surname": "Zeuner", "name": "Kirsten E.", "pid": [], "rank": 1, "affiliation": [], "fullname": "Zeuner, Kirsten E."}, {"surname": "Knutzen", "name": "Arne", "pid": [], "rank": 2, "affiliation": [], "fullname": "Knutzen, Arne"}, {"surname": "Granert", "name": "Oliver", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0002-0656-1023"}], "rank": 3, "affiliation": [], "fullname": "Granert, Oliver"}, {"surname": "Sablowsky", "name": "Simone", "pid": [], "rank": 4, "affiliation": [], "fullname": "Sablowsky, Simone"}, {"surname": "Go\\u0308tz", "name": "Julia", "pid": [], "rank": 5, "affiliation": [], "fullname": "G\\u00f6tz, Julia"}, {"surname": "Wolff", "name": "Stephan", "pid": [], "rank": 6, "affiliation": [], "fullname": "Wolff, Stephan"}, {"surname": "Jansen", "name": "Olav", "pid": [], "rank": 7, "affiliation": [], "fullname": "Jansen, Olav"}, {"surname": "Dressler", "name": "Dirk", "pid": [], "rank": 8, "affiliation": [], "fullname": "Dressler, Dirk"}, {"surname": "Schneider", "name": "Susanne A.", "pid": [], "rank": 9, "affiliation": [], "fullname": "Schneider, Susanne A."}, {"surname": "Klein", "name": "Christine", "pid": [], "rank": 10, "affiliation": [], "fullname": "Klein, Christine"}, {"surname": "Deuschl", "name": "Gu\\u0308nther", "pid": [], "rank": 11, "affiliation": [], "fullname": "Deuschl, G\\u00fcnther"}, {"surname": "Eimeren", "name": "Thilo", "pid": [], "rank": 12, "affiliation": [], "fullname": "van Eimeren, Thilo"}, {"surname": "Witt", "name": "Karsten", "pid": [], "rank": 13, "affiliation": [], "fullname": "Witt, Karsten"}], "source": [], "dateofcollection": "2017-07-27T19:04:09.131Z", "fulltext": [], "dateoftransformation": "2019-01-23T10:15:19.582Z", "description": [], "format": [], "coverage": [], "publisher": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Elsevier BV"}, "language": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "bestaccessright": {"classid": "OPEN SOURCE", "classname": "Open Source", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "country": [{"classid": "IT", "classname": "Italy", "schemeid": "dnet:countries", "schemename": "dnet:countries"}], "extraInfo": [], "originalId": ["10.1016/j.nicl.2015.11.006"], "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "title": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "main title", "classname": "main title", "schemename": "dnet:dataCite_title", "schemeid": "dnet:dataCite_title"}, "value": "Altered brain activation in a reversal learning task unmasks adaptive changes in cognitive control in writer's cramp"}]} -{"context": [], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:datasetarchive", "classname": "sysimport:crosswalk:datasetarchive", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": true, "inferenceprovenance": "dedup-similarity-result-levenstein", "invisible": false, "trust": "0.9"}, "resourcetype": {"classid": "0004", "classname": "Conference object", "schemename": "dnet:dataCite_resource", "schemeid": "dnet:dataCite_resource"}, "pid": [], "contributor": [], "resulttype": {"classid": "software", "classname": "software", "schemename": "dnet:result_typologies", "schemeid": "dnet:result_typologies"}, "relevantdate": [], "collectedfrom": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "CRIS UNS (Current Research Information System University of Novi Sad)", "key": "10|CRIS_UNS____::f66f1bd369679b5b077dcdf006089556"}], "id": "50|CrisUnsNoviS::9f9d014eea45dab432cab636c4c9cf39", "subject": [], "instance": [{"refereed": null, "hostedby": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "CRIS UNS (Current Research Information System University of Novi Sad)", "key": "10|CRIS_UNS____::f66f1bd369679b5b077dcdf006089556"}, "processingchargeamount": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "license": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "url": ["https://www.cris.uns.ac.rs/record.jsf?recordId=113444&source=OpenAIRE&language=en"], "distributionlocation": "", "processingchargecurrency": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2019-01-01"}, "collectedfrom": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "CRIS UNS (Current Research Information System University of Novi Sad)", "key": "10|CRIS_UNS____::f66f1bd369679b5b077dcdf006089556"}, "accessright": {"classid": "UNKNOWN", "classname": "UNKNOWN", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "instancetype": {"classid": "0004", "classname": "Conference object", "schemename": "dnet:dataCite_resource", "schemeid": "dnet:dataCite_resource"}}], "embargoenddate": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "lastupdatetimestamp": 0, "author": [{"surname": "Zeuner", "name": "Kirsten E.", "pid": [], "rank": 1, "affiliation": [], "fullname": "Zeuner, Kirsten E."}, {"surname": "Knutzen", "name": "Arne", "pid": [], "rank": 2, "affiliation": [], "fullname": "Knutzen, Arne"}, {"surname": "Granert", "name": "Oliver", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0002-0656-1023"}, {"qualifier": {"classid": "pubmed", "classname": "pubmed"}, "value": "pubmed.it"}], "rank": 3, "affiliation": [], "fullname": "Granert, Oliver"}, {"surname": "Sablowsky", "name": "Simone", "pid": [{"qualifier": {"classid": "id", "classname": "id"}, "value": "12345678"}], "rank": 4, "affiliation": [], "fullname": "Sablowsky, Simone"}, {"surname": "Go\\u0308tz", "name": "Julia", "pid": [], "rank": 5, "affiliation": [], "fullname": "G\\u00f6tz, Julia"}, {"surname": "Wolff", "name": "Stephan", "pid": [], "rank": 6, "affiliation": [], "fullname": "Wolff, Stephan"}, {"surname": "Jansen", "name": "Olav", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0000-0656-1023"},{"qualifier": {"classid": "id", "classname": "id"}, "value": "987654321"}], "rank": 7, "affiliation": [], "fullname": "Jansen, Olav"}, {"surname": "Dressler", "name": "Dirk", "pid": [], "rank": 8, "affiliation": [], "fullname": "Dressler, Dirk"}, {"surname": "Schneider", "name": "Susanne A.", "pid": [], "rank": 9, "affiliation": [], "fullname": "Schneider, Susanne A."}], "source": [], "dateofcollection": "2020-03-10T15:05:38.685Z", "fulltext": [], "dateoftransformation": "2020-03-11T20:11:13.15Z", "description": [], "format": [], "coverage": [], "publisher": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "language": {"classid": "en", "classname": "en", "schemename": "dnet:languages", "schemeid": "dnet:languages"}, "bestaccessright": {"classid": "UNKNOWN", "classname": "unknown", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "country": [{"classid": "FI", "classname": "Finland", "schemeid": "dnet:countries", "schemename": "dnet:countries"}], "extraInfo": [], "originalId": ["(BISIS)113444", "https://www.cris.uns.ac.rs/record.jsf?recordId=113444&source=OpenAIRE&language=en"], "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "title": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "test title", "classname": "test title", "schemename": "dnet:dataCite_title", "schemeid": "dnet:dataCite_title"}, "value": "Antichains of copies of ultrahomogeneous structures"}]} \ No newline at end of file +{"id": "50|a89337edbe55::4930db9e954866d70916cbfba9f81f97", "context": [], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:datasetarchive", "classname": "sysimport:crosswalk:datasetarchive", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "dedup-similarity-result-levenstein", "invisible": false, "trust": "0.95"}, "resourcetype": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "pid": [], "contributor": [], "resulttype": {"classid": "software", "classname": "software", "schemename": "dnet:result_typologies", "schemeid": "dnet:result_typologies"}, "relevantdate": [], "collectedfrom": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Journal.fi", "key": "10|openaire____::6eef8049d0feedc089ee009abca55e35"}], "subject": [], "instance": [{"refereed": null, "hostedby": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Journal.fi", "key": "10|openaire____::6eef8049d0feedc089ee009abca55e35"}, "processingchargeamount": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "license": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "url": [], "distributionlocation": "", "processingchargecurrency": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "collectedfrom": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Journal.fi", "key": "10|openaire____::6eef8049d0feedc089ee009abca55e35"}, "accessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "instancetype": {"classid": "0001", "classname": "Article", "schemename": "dnet:dataCite_resource", "schemeid": "dnet:dataCite_resource"}}], "embargoenddate": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "lastupdatetimestamp": 0, "author": [{"surname": "Go\\u0308tz", "name": "Julia", "pid": [], "rank": 5, "affiliation": [], "fullname": "G\\u00f6tz, Julia"}, {"surname": "Wolff", "name": "Stephan", "pid": [], "rank": 6, "affiliation": [], "fullname": "Wolff, Stephan"}, {"surname": "Jansen", "name": "Olav", "pid": [], "rank": 7, "affiliation": [], "fullname": "Jansen, Olav"}, {"surname": "Dressler", "name": "Dirk", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0000-0656-9999"},{"qualifier": {"classid": "id", "classname": "id"}, "value": "987654321"}], "rank": 8, "affiliation": [], "fullname": "Dressler, Dirk"}, {"surname": "Schneider", "name": "Susanne A.", "pid": [], "rank": 9, "affiliation": [], "fullname": "Schneider, Susanne A."}], "source": [], "dateofcollection": "2019-11-05T14:49:22.351Z", "fulltext": [], "dateoftransformation": "2019-11-05T16:10:58.988Z", "description": [], "format": [], "coverage": [], "publisher": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "language": {"classid": "eng", "classname": "English", "schemename": "dnet:languages", "schemeid": "dnet:languages"}, "bestaccessright": {"classid": "OPEN SOURCE", "classname": "Open Source", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "country": [], "extraInfo": [], "originalId": [], "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2018-09-30"}, "title": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "main title", "classname": "main title", "schemename": "dnet:dataCite_title", "schemeid": "dnet:dataCite_title"}, "value": "Altered brain activation in a reversal learning task unmasks adaptive changes in cognitive control in writer's cramp"}]} +{"id": "50|doi_________::0968af610a356656706657e4f234b340", "context": [], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:repository", "classname": "sysimport:crosswalk:repository", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": true, "inferenceprovenance": "dedup-similarity-result-levenstein", "invisible": false, "trust": "0.9"}, "resourcetype": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "pid": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "doi", "classname": "doi", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "10.1016/j.nicl.2015.11.006"}], "contributor": [], "resulttype": {"classid": "software", "classname": "software", "schemename": "dnet:result_typologies", "schemeid": "dnet:result_typologies"}, "relevantdate": [], "collectedfrom": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "BASE (Open Access Aggregator)", "key": "10|openaire____::df45502607927471ecf8a6ae83683ff5"}], "subject": [], "instance": [{"refereed": null, "hostedby": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "NeuroImage: Clinical", "key": "10|doajarticles::0c0e74daa5d95504eade9c81ebbd5b8a"}, "processingchargeamount": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "license": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "http://creativecommons.org/licenses/by-nc-nd/4.0/"}, "url": ["http://dx.doi.org/10.1016/j.nicl.2015.11.006"], "distributionlocation": "", "processingchargecurrency": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "collectedfrom": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "BASE (Open Access Aggregator)", "key": "10|openaire____::df45502607927471ecf8a6ae83683ff5"}, "accessright": {"classid": "OPEN", "classname": "Open Access", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "instancetype": {"classid": "0001", "classname": "Article", "schemename": "dnet:publication_resource", "schemeid": "dnet:publication_resource"}}], "embargoenddate": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "lastupdatetimestamp": 0, "author": [{"surname": "Zeuner", "name": "Kirsten E.", "pid": [], "rank": 1, "affiliation": [], "fullname": "Zeuner, Kirsten E."}, {"surname": "Knutzen", "name": "Arne", "pid": [], "rank": 2, "affiliation": [], "fullname": "Knutzen, Arne"}, {"surname": "Granert", "name": "Oliver", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0002-0656-1023"}], "rank": 3, "affiliation": [], "fullname": "Granert, Oliver"}, {"surname": "Sablowsky", "name": "Simone", "pid": [], "rank": 4, "affiliation": [], "fullname": "Sablowsky, Simone"}, {"surname": "Go\\u0308tz", "name": "Julia", "pid": [], "rank": 5, "affiliation": [], "fullname": "G\\u00f6tz, Julia"}, {"surname": "Wolff", "name": "Stephan", "pid": [], "rank": 6, "affiliation": [], "fullname": "Wolff, Stephan"}, {"surname": "Jansen", "name": "Olav", "pid": [], "rank": 7, "affiliation": [], "fullname": "Jansen, Olav"}, {"surname": "Dressler", "name": "Dirk", "pid": [], "rank": 8, "affiliation": [], "fullname": "Dressler, Dirk"}, {"surname": "Schneider", "name": "Susanne A.", "pid": [], "rank": 9, "affiliation": [], "fullname": "Schneider, Susanne A."}, {"surname": "Klein", "name": "Christine", "pid": [], "rank": 10, "affiliation": [], "fullname": "Klein, Christine"}, {"surname": "Deuschl", "name": "Gu\\u0308nther", "pid": [], "rank": 11, "affiliation": [], "fullname": "Deuschl, G\\u00fcnther"}, {"surname": "Eimeren", "name": "Thilo", "pid": [], "rank": 12, "affiliation": [], "fullname": "van Eimeren, Thilo"}, {"surname": "Witt", "name": "Karsten", "pid": [], "rank": 13, "affiliation": [], "fullname": "Witt, Karsten"}], "source": [], "dateofcollection": "2017-07-27T19:04:09.131Z", "fulltext": [], "dateoftransformation": "2019-01-23T10:15:19.582Z", "description": [], "format": [], "coverage": [], "publisher": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "Elsevier BV"}, "language": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "bestaccessright": {"classid": "OPEN SOURCE", "classname": "Open Source", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "country": [{"classid": "IT", "classname": "Italy", "schemeid": "dnet:countries", "schemename": "dnet:countries"}], "extraInfo": [], "originalId": ["10.1016/j.nicl.2015.11.006"], "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "title": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "main title", "classname": "main title", "schemename": "dnet:dataCite_title", "schemeid": "dnet:dataCite_title"}, "value": "Altered brain activation in a reversal learning task unmasks adaptive changes in cognitive control in writer's cramp"}]} +{"id": "50|CrisUnsNoviS::9f9d014eea45dab432cab636c4c9cf39", "context": [], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:datasetarchive", "classname": "sysimport:crosswalk:datasetarchive", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": true, "inferenceprovenance": "dedup-similarity-result-levenstein", "invisible": false, "trust": "0.9"}, "resourcetype": {"classid": "0004", "classname": "Conference object", "schemename": "dnet:dataCite_resource", "schemeid": "dnet:dataCite_resource"}, "pid": [], "contributor": [], "resulttype": {"classid": "software", "classname": "software", "schemename": "dnet:result_typologies", "schemeid": "dnet:result_typologies"}, "relevantdate": [], "collectedfrom": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "CRIS UNS (Current Research Information System University of Novi Sad)", "key": "10|CRIS_UNS____::f66f1bd369679b5b077dcdf006089556"}], "subject": [], "instance": [{"refereed": null, "hostedby": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "CRIS UNS (Current Research Information System University of Novi Sad)", "key": "10|CRIS_UNS____::f66f1bd369679b5b077dcdf006089556"}, "processingchargeamount": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "license": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "url": ["https://www.cris.uns.ac.rs/record.jsf?recordId=113444&source=OpenAIRE&language=en"], "distributionlocation": "", "processingchargecurrency": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2019-01-01"}, "collectedfrom": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "CRIS UNS (Current Research Information System University of Novi Sad)", "key": "10|CRIS_UNS____::f66f1bd369679b5b077dcdf006089556"}, "accessright": {"classid": "UNKNOWN", "classname": "UNKNOWN", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "instancetype": {"classid": "0004", "classname": "Conference object", "schemename": "dnet:dataCite_resource", "schemeid": "dnet:dataCite_resource"}}], "embargoenddate": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "lastupdatetimestamp": 0, "author": [{"surname": "Zeuner", "name": "Kirsten E.", "pid": [], "rank": 1, "affiliation": [], "fullname": "Zeuner, Kirsten E."}, {"surname": "Knutzen", "name": "Arne", "pid": [], "rank": 2, "affiliation": [], "fullname": "Knutzen, Arne"}, {"surname": "Granert", "name": "Oliver", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0002-0656-1023"}, {"qualifier": {"classid": "pubmed", "classname": "pubmed"}, "value": "pubmed.it"}], "rank": 3, "affiliation": [], "fullname": "Granert, Oliver"}, {"surname": "Sablowsky", "name": "Simone", "pid": [{"qualifier": {"classid": "id", "classname": "id"}, "value": "12345678"}], "rank": 4, "affiliation": [], "fullname": "Sablowsky, Simone"}, {"surname": "Go\\u0308tz", "name": "Julia", "pid": [], "rank": 5, "affiliation": [], "fullname": "G\\u00f6tz, Julia"}, {"surname": "Wolff", "name": "Stephan", "pid": [], "rank": 6, "affiliation": [], "fullname": "Wolff, Stephan"}, {"surname": "Jansen", "name": "Olav", "pid": [{"qualifier": {"classid": "ORCID", "classname": "ORCID"}, "value": "0000-0000-0656-1023"},{"qualifier": {"classid": "id", "classname": "id"}, "value": "987654321"}], "rank": 7, "affiliation": [], "fullname": "Jansen, Olav"}, {"surname": "Dressler", "name": "Dirk", "pid": [], "rank": 8, "affiliation": [], "fullname": "Dressler, Dirk"}, {"surname": "Schneider", "name": "Susanne A.", "pid": [], "rank": 9, "affiliation": [], "fullname": "Schneider, Susanne A."}], "source": [], "dateofcollection": "2020-03-10T15:05:38.685Z", "fulltext": [], "dateoftransformation": "2020-03-11T20:11:13.15Z", "description": [], "format": [], "coverage": [], "publisher": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": ""}, "language": {"classid": "en", "classname": "en", "schemename": "dnet:languages", "schemeid": "dnet:languages"}, "bestaccessright": {"classid": "UNKNOWN", "classname": "unknown", "schemename": "dnet:access_modes", "schemeid": "dnet:access_modes"}, "country": [{"classid": "FI", "classname": "Finland", "schemeid": "dnet:countries", "schemename": "dnet:countries"}], "extraInfo": [], "originalId": ["(BISIS)113444", "https://www.cris.uns.ac.rs/record.jsf?recordId=113444&source=OpenAIRE&language=en"], "dateofacceptance": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "value": "2016-01-01"}, "title": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "", "classname": "", "schemename": "", "schemeid": ""}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": ""}, "qualifier": {"classid": "test title", "classname": "test title", "schemename": "dnet:dataCite_title", "schemeid": "dnet:dataCite_title"}, "value": "Antichains of copies of ultrahomogeneous structures"}]} \ No newline at end of file From 2d764974882f30df66f19ab4377abab36d4894b8 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 5 Nov 2020 17:10:24 +0100 Subject: [PATCH 026/445] cleanup --- .../dhp/oa/graph/raw/GenerateEntitiesApplication.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java index 27b57b2d1..b235c3f54 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java @@ -134,10 +134,7 @@ public class GenerateEntitiesApplication { return o1; } - protected static Result mergeResults(Result o1, Result o2) { - Result r1 = o1; - Result r2 = o2; - + protected static Result mergeResults(Result r1, Result r2) { if (new ResultTypeComparator().compare(r1, r2) < 0) { r1.mergeFrom(r2); return r1; From 8e1d43aab21a1e801c7eaf85f5d3b163b02efc2e Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Mon, 9 Nov 2020 11:53:55 +0100 Subject: [PATCH 027/445] Implemented ID generation using IdentifierRecordFactory on DOIBoost --- .../java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala | 6 ++++++ .../main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala | 6 +++++- .../main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala | 8 ++++++++ .../main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala | 7 +++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala index 096217a55..4b67e08f2 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala @@ -1,6 +1,7 @@ package eu.dnetlib.doiboost.crossref import eu.dnetlib.dhp.schema.oaf._ +import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory import eu.dnetlib.dhp.utils.DHPUtils import eu.dnetlib.doiboost.DoiBoostMappingUtil._ import org.apache.commons.lang.StringUtils @@ -96,7 +97,12 @@ case object Crossref2Oaf { result.setOriginalId(tmp.filter(id => id != null).asJava) //Set identifier as 50 | doiboost____::md5(DOI) + + //IMPORTANT + //The old method result.setId(generateIdentifier(result, doi)) + //will be replaced using IdentifierFactory result.setId(generateIdentifier(result, doi)) + result.setId(IdentifierFactory.createIdentifier(result)) // Add DataInfo result.setDataInfo(generateDataInfo()) diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala index 7bb4686cf..e0ff27421 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala @@ -1,6 +1,7 @@ package eu.dnetlib.doiboost.mag +import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory import eu.dnetlib.dhp.schema.oaf.{Instance, Journal, Publication, StructuredProperty} import eu.dnetlib.doiboost.DoiBoostMappingUtil import org.json4s @@ -190,8 +191,11 @@ case object ConversionUtil { pub.setPid(List(createSP(paper.Doi.toLowerCase, "doi", PID_TYPES)).asJava) pub.setOriginalId(List(paper.PaperId.toString, paper.Doi.toLowerCase).asJava) - //Set identifier as 50|doiboost____::md5(DOI) + //IMPORTANT + //The old method result.setId(generateIdentifier(result, doi)) + //will be replaced using IdentifierFactory pub.setId(generateIdentifier(pub, paper.Doi.toLowerCase)) + pub.setId(IdentifierFactory.createIdentifier(pub)) val mainTitles = createSP(paper.PaperTitle, "main title", "dnet:dataCite_title") val originalTitles = createSP(paper.OriginalTitle, "alternative title", "dnet:dataCite_title") diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala index f230c604f..1e01ed16d 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala @@ -1,5 +1,6 @@ package eu.dnetlib.doiboost.orcid +import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory import eu.dnetlib.dhp.schema.oaf.{Author, Publication} import eu.dnetlib.doiboost.DoiBoostMappingUtil import eu.dnetlib.doiboost.DoiBoostMappingUtil.{ORCID, PID_TYPES, createSP, generateDataInfo, generateIdentifier} @@ -48,7 +49,14 @@ object ORCIDToOAF { val pub:Publication = new Publication pub.setPid(List(createSP(doi, "doi", PID_TYPES)).asJava) pub.setDataInfo(generateDataInfo()) + + //IMPORTANT + //The old method pub.setId(IdentifierFactory.createIdentifier(pub)) + //will be replaced using IdentifierFactory pub.setId(generateIdentifier(pub, doi.toLowerCase)) + pub.setId(IdentifierFactory.createIdentifier(pub)) + + try{ pub.setAuthor(input.authors.map(a=> { generateAuthor(a.name, a.surname, a.creditName, a.oid) diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala index 08cd4ee8e..c368c45a3 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala @@ -1,5 +1,6 @@ package eu.dnetlib.doiboost.uw +import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory import eu.dnetlib.dhp.schema.oaf.{Instance, Publication} import org.json4s import org.json4s.DefaultFormats @@ -33,7 +34,13 @@ object UnpayWallToOAF { val oaLocation:OALocation = (json \ "best_oa_location").extractOrElse[OALocation](null) pub.setPid(List(createSP(doi, "doi", PID_TYPES)).asJava) + + //IMPORTANT + //The old method pub.setId(IdentifierFactory.createIdentifier(pub)) + //will be replaced using IdentifierFactory pub.setId(generateIdentifier(pub, doi.toLowerCase)) + pub.setId(IdentifierFactory.createIdentifier(pub)) + pub.setCollectedfrom(List(createUnpayWallCollectedFrom()).asJava) pub.setDataInfo(generateDataInfo()) From fcbb05eb21e90f159e4ca2ffdb67550b5db71959 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 19 Nov 2020 15:14:33 +0100 Subject: [PATCH 028/445] cleanup --- .../eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java index 8231dd77e..2dd49345c 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java @@ -26,6 +26,7 @@ import eu.dnetlib.dhp.schema.oaf.Oaf; import eu.dnetlib.dhp.schema.oaf.OafEntity; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import static eu.dnetlib.dhp.oa.graph.clean.CleaningFunctions.*; public class CleanGraphSparkJob { @@ -86,9 +87,9 @@ public class CleanGraphSparkJob { final CleaningRuleMap mapping = CleaningRuleMap.create(vocs); readTableFromPath(spark, inputPath, clazz) - .map((MapFunction) value -> CleaningFunctions.fixVocabularyNames(value), Encoders.bean(clazz)) + .map((MapFunction) value -> fixVocabularyNames(value), Encoders.bean(clazz)) .map((MapFunction) value -> OafCleaner.apply(value, mapping), Encoders.bean(clazz)) - .map((MapFunction) value -> CleaningFunctions.fixDefaults(value), Encoders.bean(clazz)) + .map((MapFunction) value -> fixDefaults(value), Encoders.bean(clazz)) .write() .mode(SaveMode.Overwrite) .option("compression", "gzip") From c016cc050ac45729d41573222a8ee4b700b95086 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 23 Nov 2020 19:16:40 +0100 Subject: [PATCH 029/445] IdentifierFactory: in case a record provides more than one pid of the same type, the the lexicographically lower value is chosen as best pick --- .../schema/oaf/utils/IdentifierFactory.java | 35 ++++++++++---- .../oaf/utils/OrganizationPidComparator.java | 22 +++++---- .../dhp/schema/oaf/utils/PidComparator.java | 20 +++----- .../schema/oaf/utils/PidValueComparator.java | 35 ++++++++++++++ .../schema/oaf/utils/ResultPidComparator.java | 46 +++++++++++-------- .../oaf/utils/IdentifierFactoryTest.java | 7 +-- ...ication_doi.json => publication_doi1.json} | 0 .../schema/oaf/utils/publication_doi2.json | 1 + ...ication_pmc.json => publication_pmc1.json} | 0 ...ication_urn.json => publication_urn1.json} | 0 10 files changed, 114 insertions(+), 52 deletions(-) create mode 100644 dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidValueComparator.java rename dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/{publication_doi.json => publication_doi1.json} (100%) create mode 100644 dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi2.json rename dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/{publication_pmc.json => publication_pmc1.json} (100%) rename dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/{publication_urn.json => publication_urn1.json} (100%) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 28e4accca..a7310e8de 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -2,7 +2,12 @@ package eu.dnetlib.dhp.schema.oaf.utils; import java.io.Serializable; +import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.lang.StringUtils; @@ -42,14 +47,28 @@ public class IdentifierFactory implements Serializable { return entity.getId(); } - return entity - .getPid() - .stream() - .filter(s -> pidFilter(s)) - .min(new PidComparator<>(entity)) - .map(s -> idFromPid(entity, s)) - .map(IdentifierFactory::verifyIdSyntax) - .orElseGet(entity::getId); + Map> pids = entity + .getPid() + .stream() + .filter(s -> pidFilter(s)) + .collect( + Collectors.groupingBy(p -> p.getQualifier().getClassid(), + Collectors.mapping(p -> p, Collectors.toList())) + ); + + return pids + .values() + .stream() + .flatMap(s -> s.stream()) + .min(new PidComparator<>(entity)) + .map(min -> Optional.ofNullable(pids.get(min.getQualifier().getClassid())) + .map(p -> p.stream() + .sorted(new PidValueComparator()) + .findFirst() + .map(s -> idFromPid(entity, s)) + .orElseGet(entity::getId)) + .orElseGet(entity::getId)) + .orElseGet(entity::getId); } protected static boolean pidFilter(StructuredProperty s) { diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java index 733d41bff..a5e1b34d7 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java @@ -1,25 +1,31 @@ package eu.dnetlib.dhp.schema.oaf.utils; +import eu.dnetlib.dhp.schema.oaf.StructuredProperty; + import java.util.Comparator; -public class OrganizationPidComparator implements Comparator { +public class OrganizationPidComparator implements Comparator { @Override - public int compare(PidType pLeft, PidType pRight) { - if (pLeft.equals(PidType.GRID)) + public int compare(StructuredProperty left, StructuredProperty right) { + + PidType lClass = PidType.valueOf(left.getQualifier().getClassid()); + PidType rClass = PidType.valueOf(right.getQualifier().getClassid()); + + if (lClass.equals(PidType.GRID)) return -1; - if (pRight.equals(PidType.GRID)) + if (rClass.equals(PidType.GRID)) return 1; - if (pLeft.equals(PidType.mag_id)) + if (lClass.equals(PidType.mag_id)) return -1; - if (pRight.equals(PidType.mag_id)) + if (rClass.equals(PidType.mag_id)) return 1; - if (pLeft.equals(PidType.urn)) + if (lClass.equals(PidType.urn)) return -1; - if (pRight.equals(PidType.urn)) + if (rClass.equals(PidType.urn)) return 1; return 0; diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidComparator.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidComparator.java index 34ce5563f..2bee0eb56 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidComparator.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidComparator.java @@ -27,28 +27,22 @@ public class PidComparator implements Comparator { + + @Override + public int compare(StructuredProperty left, StructuredProperty right) { + + if (left == null && right == null) + return 0; + if (left == null) + return 1; + if (right == null) + return -1; + + StructuredProperty l = CleaningFunctions.normalizePidValue(left); + StructuredProperty r = CleaningFunctions.normalizePidValue(right); + + return Optional.ofNullable(l.getValue()) + .map(lv -> Optional.ofNullable(r.getValue()) + .map(rv -> lv.compareTo(rv)) + .orElse(-1)) + .orElse(1); + } +} diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/ResultPidComparator.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/ResultPidComparator.java index 0f65cca36..0a733495d 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/ResultPidComparator.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/ResultPidComparator.java @@ -1,55 +1,61 @@ package eu.dnetlib.dhp.schema.oaf.utils; +import eu.dnetlib.dhp.schema.oaf.StructuredProperty; + import java.util.Comparator; -public class ResultPidComparator implements Comparator { +public class ResultPidComparator implements Comparator { @Override - public int compare(PidType pLeft, PidType pRight) { - if (pLeft.equals(PidType.doi)) + public int compare(StructuredProperty left, StructuredProperty right) { + + PidType lClass = PidType.valueOf(left.getQualifier().getClassid()); + PidType rClass = PidType.valueOf(right.getQualifier().getClassid()); + + if (lClass.equals(PidType.doi)) return -1; - if (pRight.equals(PidType.doi)) + if (rClass.equals(PidType.doi)) return 1; - if (pLeft.equals(PidType.pmid)) + if (lClass.equals(PidType.pmid)) return -1; - if (pRight.equals(PidType.pmid)) + if (rClass.equals(PidType.pmid)) return 1; - if (pLeft.equals(PidType.pmc)) + if (lClass.equals(PidType.pmc)) return -1; - if (pRight.equals(PidType.pmc)) + if (rClass.equals(PidType.pmc)) return 1; - if (pLeft.equals(PidType.handle)) + if (lClass.equals(PidType.handle)) return -1; - if (pRight.equals(PidType.handle)) + if (rClass.equals(PidType.handle)) return 1; - if (pLeft.equals(PidType.arXiv)) + if (lClass.equals(PidType.arXiv)) return -1; - if (pRight.equals(PidType.arXiv)) + if (rClass.equals(PidType.arXiv)) return 1; - if (pLeft.equals(PidType.NCID)) + if (lClass.equals(PidType.NCID)) return -1; - if (pRight.equals(PidType.NCID)) + if (rClass.equals(PidType.NCID)) return 1; - if (pLeft.equals(PidType.GBIF)) + if (lClass.equals(PidType.GBIF)) return -1; - if (pRight.equals(PidType.GBIF)) + if (rClass.equals(PidType.GBIF)) return 1; - if (pLeft.equals(PidType.nct)) + if (lClass.equals(PidType.nct)) return -1; - if (pRight.equals(PidType.nct)) + if (rClass.equals(PidType.nct)) return 1; - if (pLeft.equals(PidType.urn)) + if (lClass.equals(PidType.urn)) return -1; - if (pRight.equals(PidType.urn)) + if (rClass.equals(PidType.urn)) return 1; return 0; diff --git a/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java index d458c613e..2b34a46ca 100644 --- a/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java +++ b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java @@ -22,10 +22,11 @@ public class IdentifierFactoryTest { @Test public void testCreateIdentifierForPublication() throws IOException { - verifyIdentifier("publication_doi.json", "50|doi_________::" + DHPUtils.md5("10.1016/j.cmet.2011.03.013")); - verifyIdentifier("publication_pmc.json", "50|pmc_________::" + DHPUtils.md5("21459329")); + verifyIdentifier("publication_doi1.json", "50|doi_________::" + DHPUtils.md5("10.1016/j.cmet.2011.03.013")); + verifyIdentifier("publication_doi2.json", "50|doi_________::" + DHPUtils.md5("10.1016/j.cmet.2010.03.013")); + verifyIdentifier("publication_pmc1.json", "50|pmc_________::" + DHPUtils.md5("21459329")); verifyIdentifier( - "publication_urn.json", + "publication_urn1.json", "50|urn_________::" + DHPUtils.md5("urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2")); final String defaultID = "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f"; diff --git a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi.json b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi1.json similarity index 100% rename from dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi.json rename to dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi1.json diff --git a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi2.json b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi2.json new file mode 100644 index 000000000..7cc27f440 --- /dev/null +++ b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi2.json @@ -0,0 +1 @@ +{"id":"50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f","pid":[{"qualifier":{"classid":"doi"},"value":"10.1016/j.cmet.2011.03.013"},{"qualifier":{"classid":"doi"},"value":"10.1016/j.cmet.2010.03.013"},{"qualifier":{"classid":"urn"},"value":"urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2"},{"qualifier":{"classid":"scp-number"},"value":"79953761260"},{"qualifier":{"classid":"pmc"},"value":"21459329"}]} \ No newline at end of file diff --git a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_pmc.json b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_pmc1.json similarity index 100% rename from dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_pmc.json rename to dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_pmc1.json diff --git a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_urn.json b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_urn1.json similarity index 100% rename from dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_urn.json rename to dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_urn1.json From e43ab07af602ab1da553ab23441dc37aa92646ed Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 24 Nov 2020 14:41:39 +0100 Subject: [PATCH 030/445] code formatting --- .../schema/oaf/utils/IdentifierFactory.java | 43 +++++++++++-------- .../oaf/utils/OrganizationPidComparator.java | 6 +-- .../schema/oaf/utils/PidValueComparator.java | 19 ++++---- .../schema/oaf/utils/ResultPidComparator.java | 4 +- 4 files changed, 40 insertions(+), 32 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index a7310e8de..6090b8e2f 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -48,27 +48,32 @@ public class IdentifierFactory implements Serializable { } Map> pids = entity - .getPid() - .stream() - .filter(s -> pidFilter(s)) - .collect( - Collectors.groupingBy(p -> p.getQualifier().getClassid(), - Collectors.mapping(p -> p, Collectors.toList())) - ); + .getPid() + .stream() + .filter(s -> pidFilter(s)) + .collect( + Collectors + .groupingBy( + p -> p.getQualifier().getClassid(), + Collectors.mapping(p -> p, Collectors.toList()))); return pids - .values() - .stream() - .flatMap(s -> s.stream()) - .min(new PidComparator<>(entity)) - .map(min -> Optional.ofNullable(pids.get(min.getQualifier().getClassid())) - .map(p -> p.stream() - .sorted(new PidValueComparator()) - .findFirst() - .map(s -> idFromPid(entity, s)) - .orElseGet(entity::getId)) - .orElseGet(entity::getId)) - .orElseGet(entity::getId); + .values() + .stream() + .flatMap(s -> s.stream()) + .min(new PidComparator<>(entity)) + .map( + min -> Optional + .ofNullable(pids.get(min.getQualifier().getClassid())) + .map( + p -> p + .stream() + .sorted(new PidValueComparator()) + .findFirst() + .map(s -> idFromPid(entity, s)) + .orElseGet(entity::getId)) + .orElseGet(entity::getId)) + .orElseGet(entity::getId); } protected static boolean pidFilter(StructuredProperty s) { diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java index a5e1b34d7..8cca517f9 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java @@ -1,10 +1,10 @@ package eu.dnetlib.dhp.schema.oaf.utils; -import eu.dnetlib.dhp.schema.oaf.StructuredProperty; - import java.util.Comparator; +import eu.dnetlib.dhp.schema.oaf.StructuredProperty; + public class OrganizationPidComparator implements Comparator { @Override @@ -12,7 +12,7 @@ public class OrganizationPidComparator implements Comparator PidType lClass = PidType.valueOf(left.getQualifier().getClassid()); PidType rClass = PidType.valueOf(right.getQualifier().getClassid()); - + if (lClass.equals(PidType.GRID)) return -1; if (rClass.equals(PidType.GRID)) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidValueComparator.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidValueComparator.java index 3ac2381cc..087bbc121 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidValueComparator.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidValueComparator.java @@ -1,6 +1,9 @@ package eu.dnetlib.dhp.schema.oaf.utils; +import java.util.Comparator; +import java.util.Optional; + import eu.dnetlib.dhp.oa.graph.clean.CleaningFunctions; import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.schema.oaf.OafEntity; @@ -8,9 +11,6 @@ import eu.dnetlib.dhp.schema.oaf.Organization; import eu.dnetlib.dhp.schema.oaf.Result; import eu.dnetlib.dhp.schema.oaf.StructuredProperty; -import java.util.Comparator; -import java.util.Optional; - public class PidValueComparator implements Comparator { @Override @@ -26,10 +26,13 @@ public class PidValueComparator implements Comparator { StructuredProperty l = CleaningFunctions.normalizePidValue(left); StructuredProperty r = CleaningFunctions.normalizePidValue(right); - return Optional.ofNullable(l.getValue()) - .map(lv -> Optional.ofNullable(r.getValue()) - .map(rv -> lv.compareTo(rv)) - .orElse(-1)) - .orElse(1); + return Optional + .ofNullable(l.getValue()) + .map( + lv -> Optional + .ofNullable(r.getValue()) + .map(rv -> lv.compareTo(rv)) + .orElse(-1)) + .orElse(1); } } diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/ResultPidComparator.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/ResultPidComparator.java index 0a733495d..38c7743a2 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/ResultPidComparator.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/ResultPidComparator.java @@ -1,10 +1,10 @@ package eu.dnetlib.dhp.schema.oaf.utils; -import eu.dnetlib.dhp.schema.oaf.StructuredProperty; - import java.util.Comparator; +import eu.dnetlib.dhp.schema.oaf.StructuredProperty; + public class ResultPidComparator implements Comparator { @Override From 33bae02451e469e3f9f521a6513c027f2fdacace Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 24 Nov 2020 14:42:33 +0100 Subject: [PATCH 031/445] reverted behaviour of the cleaning workflow: grouping entities by ID will be managed differently --- .../GroupEntitiesAndRelationsSparkJob.java | 206 ------------------ .../dhp/oa/graph/clean/oozie_app/workflow.xml | 42 +--- 2 files changed, 9 insertions(+), 239 deletions(-) delete mode 100644 dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/GroupEntitiesAndRelationsSparkJob.java diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/GroupEntitiesAndRelationsSparkJob.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/GroupEntitiesAndRelationsSparkJob.java deleted file mode 100644 index 9c80528e3..000000000 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/GroupEntitiesAndRelationsSparkJob.java +++ /dev/null @@ -1,206 +0,0 @@ - -package eu.dnetlib.dhp.oa.graph.clean; - -import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; -import static eu.dnetlib.dhp.utils.DHPUtils.toSeq; - -import java.io.IOException; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.spark.SparkConf; -import org.apache.spark.api.java.JavaSparkContext; -import org.apache.spark.api.java.function.FilterFunction; -import org.apache.spark.api.java.function.MapFunction; -import org.apache.spark.sql.*; -import org.apache.spark.sql.expressions.Aggregator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.jayway.jsonpath.Configuration; -import com.jayway.jsonpath.DocumentContext; -import com.jayway.jsonpath.JsonPath; -import com.jayway.jsonpath.Option; - -import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.common.HdfsSupport; -import eu.dnetlib.dhp.schema.common.ModelSupport; -import eu.dnetlib.dhp.schema.oaf.*; -import scala.Tuple2; - -/** - * Groups the graph content by entity identifier to ensure ID uniqueness - */ -public class GroupEntitiesAndRelationsSparkJob { - - private static final Logger log = LoggerFactory.getLogger(GroupEntitiesAndRelationsSparkJob.class); - - private final static String ID_JPATH = "$.id"; - - private final static String SOURCE_JPATH = "$.source"; - - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - public static void main(String[] args) throws Exception { - - String jsonConfiguration = IOUtils - .toString( - GroupEntitiesAndRelationsSparkJob.class - .getResourceAsStream( - "/eu/dnetlib/dhp/oa/graph/group_graph_entities_parameters.json")); - final ArgumentApplicationParser parser = new ArgumentApplicationParser(jsonConfiguration); - parser.parseArgument(args); - - Boolean isSparkSessionManaged = Optional - .ofNullable(parser.get("isSparkSessionManaged")) - .map(Boolean::valueOf) - .orElse(Boolean.TRUE); - log.info("isSparkSessionManaged: {}", isSparkSessionManaged); - - String graphInputPath = parser.get("graphInputPath"); - log.info("graphInputPath: {}", graphInputPath); - - String outputPath = parser.get("outputPath"); - log.info("outputPath: {}", outputPath); - - SparkConf conf = new SparkConf(); - conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer"); - conf.registerKryoClasses(ModelSupport.getOafModelClasses()); - - runWithSparkSession( - conf, - isSparkSessionManaged, - spark -> { - HdfsSupport.remove(outputPath, spark.sparkContext().hadoopConfiguration()); - groupEntitiesAndRelations(spark, graphInputPath, outputPath); - }); - } - - private static void groupEntitiesAndRelations( - SparkSession spark, - String inputPath, - String outputPath) { - - TypedColumn aggregator = new GroupingAggregator().toColumn(); - final JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); - spark - .read() - .textFile(toSeq(listPaths(inputPath, sc))) - .map((MapFunction) s -> parseOaf(s), Encoders.kryo(Oaf.class)) - .filter((FilterFunction) oaf -> StringUtils.isNotBlank(ModelSupport.idFn().apply(oaf))) - .groupByKey((MapFunction) oaf -> ModelSupport.idFn().apply(oaf), Encoders.STRING()) - .agg(aggregator) - .map( - (MapFunction, String>) t -> t._2().getClass().getName() + - "|" + OBJECT_MAPPER.writeValueAsString(t._2()), - Encoders.STRING()) - .write() - .option("compression", "gzip") - .mode(SaveMode.Overwrite) - .text(outputPath); - } - - public static class GroupingAggregator extends Aggregator { - - @Override - public Oaf zero() { - return null; - } - - @Override - public Oaf reduce(Oaf b, Oaf a) { - return mergeAndGet(b, a); - } - - private Oaf mergeAndGet(Oaf b, Oaf a) { - if (Objects.nonNull(a) && Objects.nonNull(b)) { - return OafMapperUtils.merge(b, a); - } - return Objects.isNull(a) ? b : a; - } - - @Override - public Oaf merge(Oaf b, Oaf a) { - return mergeAndGet(b, a); - } - - @Override - public Oaf finish(Oaf j) { - return j; - } - - @Override - public Encoder bufferEncoder() { - return Encoders.kryo(Oaf.class); - } - - @Override - public Encoder outputEncoder() { - return Encoders.kryo(Oaf.class); - } - - } - - private static Oaf parseOaf(String s) { - - DocumentContext dc = JsonPath - .parse(s, Configuration.defaultConfiguration().addOptions(Option.SUPPRESS_EXCEPTIONS)); - final String id = dc.read(ID_JPATH); - if (StringUtils.isNotBlank(id)) { - - String prefix = StringUtils.substringBefore(id, "|"); - switch (prefix) { - case "10": - return parse(s, Datasource.class); - case "20": - return parse(s, Organization.class); - case "40": - return parse(s, Project.class); - case "50": - String resultType = dc.read("$.resulttype.classid"); - switch (resultType) { - case "publication": - return parse(s, Publication.class); - case "dataset": - return parse(s, eu.dnetlib.dhp.schema.oaf.Dataset.class); - case "software": - return parse(s, Software.class); - case "other": - return parse(s, OtherResearchProduct.class); - default: - throw new IllegalArgumentException(String.format("invalid resultType: '%s'", resultType)); - } - default: - throw new IllegalArgumentException(String.format("invalid id prefix: '%s'", prefix)); - } - } else { - String source = dc.read(SOURCE_JPATH); - if (StringUtils.isNotBlank(source)) { - return parse(s, Relation.class); - } else { - throw new IllegalArgumentException(String.format("invalid oaf: '%s'", s)); - } - } - } - - private static Oaf parse(String s, Class clazz) { - try { - return OBJECT_MAPPER.readValue(s, clazz); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private static List listPaths(String inputPath, JavaSparkContext sc) { - return HdfsSupport - .listFiles(inputPath, sc.hadoopConfiguration()) - .stream() - .collect(Collectors.toList()); - } - -} diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/clean/oozie_app/workflow.xml b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/clean/oozie_app/workflow.xml index 992d8c40e..dc0529012 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/clean/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/clean/oozie_app/workflow.xml @@ -50,36 +50,12 @@ - + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] - - - yarn - cluster - group graph entities and relations - eu.dnetlib.dhp.oa.graph.clean.GroupEntitiesAndRelationsSparkJob - dhp-graph-mapper-${projectVersion}.jar - - --executor-cores=${sparkExecutorCores} - --executor-memory=${sparkExecutorMemory} - --driver-memory=${sparkDriverMemory} - --conf spark.extraListeners=${spark2ExtraListeners} - --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} - --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} - --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} - --conf spark.sql.shuffle.partitions=7680 - - --graphInputPath${graphInputPath} - --outputPath${workingDir}/grouped_entities - - - - - @@ -108,7 +84,7 @@ --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} --conf spark.sql.shuffle.partitions=7680 - --inputPath${workingDir}/grouped_entities + --inputPath${graphInputPath}/publication --outputPath${graphOutputPath}/publication --graphTableClassNameeu.dnetlib.dhp.schema.oaf.Publication --isLookupUrl${isLookupUrl} @@ -134,7 +110,7 @@ --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} --conf spark.sql.shuffle.partitions=7680 - --inputPath${workingDir}/grouped_entities + --inputPath${graphInputPath}/dataset --outputPath${graphOutputPath}/dataset --graphTableClassNameeu.dnetlib.dhp.schema.oaf.Dataset --isLookupUrl${isLookupUrl} @@ -160,7 +136,7 @@ --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} --conf spark.sql.shuffle.partitions=7680 - --inputPath${workingDir}/grouped_entities + --inputPath${graphInputPath}/otherresearchproduct --outputPath${graphOutputPath}/otherresearchproduct --graphTableClassNameeu.dnetlib.dhp.schema.oaf.OtherResearchProduct --isLookupUrl${isLookupUrl} @@ -186,7 +162,7 @@ --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} --conf spark.sql.shuffle.partitions=7680 - --inputPath${workingDir}/grouped_entities + --inputPath${graphInputPath}/software --outputPath${graphOutputPath}/software --graphTableClassNameeu.dnetlib.dhp.schema.oaf.Software --isLookupUrl${isLookupUrl} @@ -212,7 +188,7 @@ --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} --conf spark.sql.shuffle.partitions=7680 - --inputPath${workingDir}/grouped_entities + --inputPath${graphInputPath}/datasource --outputPath${graphOutputPath}/datasource --graphTableClassNameeu.dnetlib.dhp.schema.oaf.Datasource --isLookupUrl${isLookupUrl} @@ -238,7 +214,7 @@ --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} --conf spark.sql.shuffle.partitions=7680 - --inputPath${workingDir}/grouped_entities + --inputPath${graphInputPath}/organization --outputPath${graphOutputPath}/organization --graphTableClassNameeu.dnetlib.dhp.schema.oaf.Organization --isLookupUrl${isLookupUrl} @@ -264,7 +240,7 @@ --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} --conf spark.sql.shuffle.partitions=7680 - --inputPath${workingDir}/grouped_entities + --inputPath${graphInputPath}/project --outputPath${graphOutputPath}/project --graphTableClassNameeu.dnetlib.dhp.schema.oaf.Project --isLookupUrl${isLookupUrl} @@ -290,7 +266,7 @@ --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} --conf spark.sql.shuffle.partitions=7680 - --inputPath${workingDir}/grouped_entities + --inputPath${graphInputPath}/relation --outputPath${graphOutputPath}/relation --graphTableClassNameeu.dnetlib.dhp.schema.oaf.Relation --isLookupUrl${isLookupUrl} From e1a1bb3ee4f91223d71b76b22170999cec210ed9 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 24 Nov 2020 18:34:03 +0100 Subject: [PATCH 032/445] moved class CleaningFunctions in the correct package. Remove newlines from titles, descriptions, subjects --- .../dhp/schema/oaf/CleaningFunctions.java | 39 +++++++++++++++++-- .../schema/oaf/utils/IdentifierFactory.java | 3 +- .../schema/oaf/utils/PidValueComparator.java | 7 +--- .../oa/graph/clean/CleanGraphSparkJob.java | 4 +- .../dhp/oa/graph/raw/OafToOafMapper.java | 2 +- .../dhp/oa/graph/raw/OdfToOafMapper.java | 1 - .../oa/graph/clean/CleaningFunctionTest.java | 7 +--- 7 files changed, 43 insertions(+), 20 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index 390af6a97..0fae82a8a 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.oa.graph.clean; +package eu.dnetlib.dhp.schema.oaf; import java.util.*; import java.util.function.Function; @@ -10,12 +10,12 @@ import org.apache.commons.lang3.StringUtils; import com.clearspring.analytics.util.Lists; import eu.dnetlib.dhp.schema.common.ModelConstants; -import eu.dnetlib.dhp.schema.oaf.*; public class CleaningFunctions { public static final String DOI_URL_PREFIX_REGEX = "(^http(s?):\\/\\/)(((dx\\.)?doi\\.org)|(handle\\.test\\.datacite\\.org))\\/"; public static final String ORCID_PREFIX_REGEX = "^http(s?):\\/\\/orcid\\.org\\/"; + public static final String NEWLINES = "(?:\\n|\\r)"; public static final Set PID_BLACKLIST = new HashSet<>(); @@ -76,7 +76,7 @@ public class CleaningFunctions { return value; } - protected static T fixDefaults(T value) { + public static T cleanup(T value) { if (value instanceof Datasource) { // nothing to clean here } else if (value instanceof Project) { @@ -109,6 +109,29 @@ public class CleaningFunctions { .filter(sp -> StringUtils.isNotBlank(sp.getValue())) .filter(sp -> Objects.nonNull(sp.getQualifier())) .filter(sp -> StringUtils.isNotBlank(sp.getQualifier().getClassid())) + .map(CleaningFunctions::removeNewLines) + .collect(Collectors.toList())); + } + if (Objects.nonNull(r.getTitle())) { + r + .setTitle( + r + .getTitle() + .stream() + .filter(Objects::nonNull) + .filter(sp -> StringUtils.isNotBlank(sp.getValue())) + .map(CleaningFunctions::removeNewLines) + .collect(Collectors.toList())); + } + if (Objects.nonNull(r.getDescription())) { + r + .setDescription( + r + .getDescription() + .stream() + .filter(Objects::nonNull) + .filter(sp -> StringUtils.isNotBlank(sp.getValue())) + .map(CleaningFunctions::removeNewLines) .collect(Collectors.toList())); } if (Objects.nonNull(r.getPid())) { @@ -205,6 +228,16 @@ public class CleaningFunctions { return value; } + protected static StructuredProperty removeNewLines(StructuredProperty s) { + s.setValue(s.getValue().replaceAll(NEWLINES, " ")); + return s; + } + + protected static Field removeNewLines(Field s) { + s.setValue(s.getValue().replaceAll(NEWLINES, " ")); + return s; + } + // HELPERS private static void fixVocabName(Qualifier q, String vocabularyName) { diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 6090b8e2f..01bb92bf6 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -7,11 +7,10 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.apache.commons.lang.StringUtils; -import eu.dnetlib.dhp.oa.graph.clean.CleaningFunctions; +import eu.dnetlib.dhp.schema.oaf.CleaningFunctions; import eu.dnetlib.dhp.schema.oaf.OafEntity; import eu.dnetlib.dhp.schema.oaf.StructuredProperty; import eu.dnetlib.dhp.utils.DHPUtils; diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidValueComparator.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidValueComparator.java index 087bbc121..7e53ba9b7 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidValueComparator.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidValueComparator.java @@ -4,12 +4,7 @@ package eu.dnetlib.dhp.schema.oaf.utils; import java.util.Comparator; import java.util.Optional; -import eu.dnetlib.dhp.oa.graph.clean.CleaningFunctions; -import eu.dnetlib.dhp.schema.common.ModelSupport; -import eu.dnetlib.dhp.schema.oaf.OafEntity; -import eu.dnetlib.dhp.schema.oaf.Organization; -import eu.dnetlib.dhp.schema.oaf.Result; -import eu.dnetlib.dhp.schema.oaf.StructuredProperty; +import eu.dnetlib.dhp.schema.oaf.*; public class PidValueComparator implements Comparator { diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java index 2dd49345c..04a5ef38d 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java @@ -2,6 +2,7 @@ package eu.dnetlib.dhp.oa.graph.clean; import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; +import static eu.dnetlib.dhp.schema.oaf.CleaningFunctions.*; import java.util.Optional; @@ -26,7 +27,6 @@ import eu.dnetlib.dhp.schema.oaf.Oaf; import eu.dnetlib.dhp.schema.oaf.OafEntity; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import static eu.dnetlib.dhp.oa.graph.clean.CleaningFunctions.*; public class CleanGraphSparkJob { @@ -89,7 +89,7 @@ public class CleanGraphSparkJob { readTableFromPath(spark, inputPath, clazz) .map((MapFunction) value -> fixVocabularyNames(value), Encoders.bean(clazz)) .map((MapFunction) value -> OafCleaner.apply(value, mapping), Encoders.bean(clazz)) - .map((MapFunction) value -> fixDefaults(value), Encoders.bean(clazz)) + .map((MapFunction) value -> cleanup(value), Encoders.bean(clazz)) .write() .mode(SaveMode.Overwrite) .option("compression", "gzip") diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java index af1a9aec6..e28e8bd3c 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java @@ -16,9 +16,9 @@ import org.dom4j.Node; import com.google.common.collect.Lists; import eu.dnetlib.dhp.common.PacePerson; -import eu.dnetlib.dhp.oa.graph.clean.CleaningFunctions; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; import eu.dnetlib.dhp.schema.oaf.*; +import eu.dnetlib.dhp.schema.oaf.CleaningFunctions; public class OafToOafMapper extends AbstractMdRecordToOafMapper { diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java index 25ff4ae88..6ceaa405a 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java @@ -12,7 +12,6 @@ import org.dom4j.Document; import org.dom4j.Node; import eu.dnetlib.dhp.common.PacePerson; -import eu.dnetlib.dhp.oa.graph.clean.CleaningFunctions; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; import eu.dnetlib.dhp.schema.oaf.*; diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java index cb34b0cb3..e38101f82 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java @@ -19,10 +19,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; -import eu.dnetlib.dhp.schema.oaf.Publication; -import eu.dnetlib.dhp.schema.oaf.Qualifier; -import eu.dnetlib.dhp.schema.oaf.Result; -import eu.dnetlib.dhp.schema.oaf.StructuredProperty; +import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; @@ -89,7 +86,7 @@ public class CleaningFunctionTest { .map(p -> p.getQualifier()) .allMatch(q -> pidTerms.contains(q.getClassid()))); - Publication p_defaults = CleaningFunctions.fixDefaults(p_out); + Publication p_defaults = CleaningFunctions.cleanup(p_out); assertEquals("CLOSED", p_defaults.getBestaccessright().getClassid()); assertNull(p_out.getPublisher()); From 36173c13a5691fc2c2799bf633b431299954c75c Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 25 Nov 2020 10:24:42 +0100 Subject: [PATCH 033/445] reverted filters in the clening process --- .../eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java index 04a5ef38d..3067d8639 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java @@ -103,15 +103,9 @@ public class CleanGraphSparkJob { return spark .read() .textFile(inputEntityPath) - .filter((FilterFunction) s -> isEntityType(s, clazz)) - .map((MapFunction) s -> StringUtils.substringAfter(s, "|"), Encoders.STRING()) .map( (MapFunction) value -> OBJECT_MAPPER.readValue(value, clazz), Encoders.bean(clazz)); } - private static boolean isEntityType(final String s, final Class clazz) { - return StringUtils.substringBefore(s, "|").equals(clazz.getName()); - } - } From dfd6205b953cb9c75c26af415d31fdbd00d31e3b Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 25 Nov 2020 14:55:32 +0100 Subject: [PATCH 034/445] Consistency graph workflow merges all the entities by ID --- .../dhp/schema/oaf/OafMapperUtils.java | 30 +- .../oa/dedup/DispatchEntitiesSparkJob.java | 93 ++++++ .../dhp/oa/dedup/GroupEntitiesSparkJob.java | 202 +++++++++++++ .../dhp/oa/dedup/SparkPropagateRelation.java | 13 +- .../dedup/consistency/oozie_app/workflow.xml | 280 ++++++++++++------ .../dedup/dispatch_entities_parameters.json | 26 ++ .../group_graph_entities_parameters.json | 0 .../dedup/propagateRelation_parameters.json | 2 +- 8 files changed, 539 insertions(+), 107 deletions(-) create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DispatchEntitiesSparkJob.java create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/GroupEntitiesSparkJob.java create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/dispatch_entities_parameters.json rename dhp-workflows/{dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph => dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup}/group_graph_entities_parameters.json (100%) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java index 301afacce..6e5857505 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java @@ -19,24 +19,24 @@ public class OafMapperUtils { public static Oaf merge(final Oaf o1, final Oaf o2) { if (ModelSupport.isSubClass(o1, OafEntity.class)) { - if (ModelSupport.isSubClass(o1, Result.class)) { - - return mergeResults((Result) o1, (Result) o2); - } else if (ModelSupport.isSubClass(o1, Datasource.class)) { - ((Datasource) o1).mergeFrom((Datasource) o2); - } else if (ModelSupport.isSubClass(o1, Organization.class)) { - ((Organization) o1).mergeFrom((Organization) o2); - } else if (ModelSupport.isSubClass(o1, Project.class)) { - ((Project) o1).mergeFrom((Project) o2); - } else { - throw new RuntimeException("invalid OafEntity subtype:" + o1.getClass().getCanonicalName()); - } + return mergeEntities((OafEntity) o1, (OafEntity) o2); } else if (ModelSupport.isSubClass(o1, Relation.class)) { ((Relation) o1).mergeFrom((Relation) o2); - } else { - throw new RuntimeException("invalid Oaf type:" + o1.getClass().getCanonicalName()); } - return o1; + throw new RuntimeException("invalid Oaf type:" + o1.getClass().getCanonicalName()); + } + + public static OafEntity mergeEntities(OafEntity e1, OafEntity e2) { + if (ModelSupport.isSubClass(e1, Result.class)) { + return mergeResults((Result) e1, (Result) e2); + } else if (ModelSupport.isSubClass(e1, Datasource.class)) { + ((Datasource) e1).mergeFrom((Datasource) e2); + } else if (ModelSupport.isSubClass(e1, Organization.class)) { + ((Organization) e1).mergeFrom((Organization) e2); + } else if (ModelSupport.isSubClass(e1, Project.class)) { + ((Project) e1).mergeFrom((Project) e2); + } + throw new RuntimeException("invalid OafEntity subtype:" + e1.getClass().getCanonicalName()); } public static Result mergeResults(Result r1, Result r2) { diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DispatchEntitiesSparkJob.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DispatchEntitiesSparkJob.java new file mode 100644 index 000000000..5506b5470 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DispatchEntitiesSparkJob.java @@ -0,0 +1,93 @@ + +package eu.dnetlib.dhp.oa.dedup; + +import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; + +import java.util.Optional; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.function.FilterFunction; +import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.SaveMode; +import org.apache.spark.sql.SparkSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.common.HdfsSupport; +import eu.dnetlib.dhp.schema.oaf.Oaf; +import eu.dnetlib.dhp.schema.oaf.OafEntity; + +public class DispatchEntitiesSparkJob { + + private static final Logger log = LoggerFactory.getLogger(DispatchEntitiesSparkJob.class); + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + public static void main(String[] args) throws Exception { + + String jsonConfiguration = IOUtils + .toString( + DispatchEntitiesSparkJob.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/dispatch_entities_parameters.json")); + final ArgumentApplicationParser parser = new ArgumentApplicationParser(jsonConfiguration); + parser.parseArgument(args); + + Boolean isSparkSessionManaged = Optional + .ofNullable(parser.get("isSparkSessionManaged")) + .map(Boolean::valueOf) + .orElse(Boolean.TRUE); + log.info("isSparkSessionManaged: {}", isSparkSessionManaged); + + String inputPath = parser.get("inputPath"); + log.info("inputPath: {}", inputPath); + + String outputPath = parser.get("outputPath"); + log.info("outputPath: {}", outputPath); + + String graphTableClassName = parser.get("graphTableClassName"); + log.info("graphTableClassName: {}", graphTableClassName); + + Class entityClazz = (Class) Class.forName(graphTableClassName); + + SparkConf conf = new SparkConf(); + runWithSparkSession( + conf, + isSparkSessionManaged, + spark -> { + HdfsSupport.remove(outputPath, spark.sparkContext().hadoopConfiguration()); + dispatchEntities(spark, inputPath, entityClazz, outputPath); + }); + } + + private static void dispatchEntities( + SparkSession spark, + String inputPath, + Class clazz, + String outputPath) { + + spark + .read() + .textFile(inputPath) + .filter((FilterFunction) s -> isEntityType(s, clazz)) + .map((MapFunction) s -> StringUtils.substringAfter(s, "|"), Encoders.STRING()) + .map( + (MapFunction) value -> OBJECT_MAPPER.readValue(value, clazz), + Encoders.bean(clazz)) + .write() + .mode(SaveMode.Overwrite) + .option("compression", "gzip") + .json(outputPath); + } + + private static boolean isEntityType(final String s, final Class clazz) { + return StringUtils.substringBefore(s, "|").equals(clazz.getName()); + } + +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/GroupEntitiesSparkJob.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/GroupEntitiesSparkJob.java new file mode 100644 index 000000000..5835617fb --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/GroupEntitiesSparkJob.java @@ -0,0 +1,202 @@ + +package eu.dnetlib.dhp.oa.dedup; + +import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; +import static eu.dnetlib.dhp.utils.DHPUtils.toSeq; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaSparkContext; +import org.apache.spark.api.java.function.FilterFunction; +import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.sql.*; +import org.apache.spark.sql.expressions.Aggregator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.jayway.jsonpath.Configuration; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.Option; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.common.HdfsSupport; +import eu.dnetlib.dhp.schema.common.ModelSupport; +import eu.dnetlib.dhp.schema.oaf.*; +import scala.Tuple2; + +/** + * Groups the graph content by entity identifier to ensure ID uniqueness + */ +public class GroupEntitiesSparkJob { + + private static final Logger log = LoggerFactory.getLogger(GroupEntitiesSparkJob.class); + + private final static String ID_JPATH = "$.id"; + + private final static String SOURCE_JPATH = "$.source"; + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + public static void main(String[] args) throws Exception { + + String jsonConfiguration = IOUtils + .toString( + GroupEntitiesSparkJob.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/group_graph_entities_parameters.json")); + final ArgumentApplicationParser parser = new ArgumentApplicationParser(jsonConfiguration); + parser.parseArgument(args); + + Boolean isSparkSessionManaged = Optional + .ofNullable(parser.get("isSparkSessionManaged")) + .map(Boolean::valueOf) + .orElse(Boolean.TRUE); + log.info("isSparkSessionManaged: {}", isSparkSessionManaged); + + String graphInputPath = parser.get("graphInputPath"); + log.info("graphInputPath: {}", graphInputPath); + + String outputPath = parser.get("outputPath"); + log.info("outputPath: {}", outputPath); + + SparkConf conf = new SparkConf(); + conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer"); + conf.registerKryoClasses(ModelSupport.getOafModelClasses()); + + runWithSparkSession( + conf, + isSparkSessionManaged, + spark -> { + HdfsSupport.remove(outputPath, spark.sparkContext().hadoopConfiguration()); + groupEntities(spark, graphInputPath, outputPath); + }); + } + + private static void groupEntities( + SparkSession spark, + String inputPath, + String outputPath) { + + final TypedColumn aggregator = new GroupingAggregator().toColumn(); + final JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); + spark + .read() + .textFile(toSeq(listPaths(inputPath, sc))) + .map((MapFunction) s -> parseOaf(s), Encoders.kryo(OafEntity.class)) + .filter((FilterFunction) e -> StringUtils.isNotBlank(ModelSupport.idFn().apply(e))) + .groupByKey((MapFunction) oaf -> ModelSupport.idFn().apply(oaf), Encoders.STRING()) + .agg(aggregator) + .map( + (MapFunction, String>) t -> t._2().getClass().getName() + + "|" + OBJECT_MAPPER.writeValueAsString(t._2()), + Encoders.STRING()) + .write() + .option("compression", "gzip") + .mode(SaveMode.Overwrite) + .text(outputPath); + } + + public static class GroupingAggregator extends Aggregator { + + @Override + public OafEntity zero() { + return null; + } + + @Override + public OafEntity reduce(OafEntity b, OafEntity a) { + return mergeAndGet(b, a); + } + + private OafEntity mergeAndGet(OafEntity b, OafEntity a) { + if (Objects.nonNull(a) && Objects.nonNull(b)) { + return OafMapperUtils.mergeEntities(b, a); + } + return Objects.isNull(a) ? b : a; + } + + @Override + public OafEntity merge(OafEntity b, OafEntity a) { + return mergeAndGet(b, a); + } + + @Override + public OafEntity finish(OafEntity j) { + return j; + } + + @Override + public Encoder bufferEncoder() { + return Encoders.kryo(OafEntity.class); + } + + @Override + public Encoder outputEncoder() { + return Encoders.kryo(OafEntity.class); + } + + } + + private static OafEntity parseOaf(String s) { + + DocumentContext dc = JsonPath + .parse(s, Configuration.defaultConfiguration().addOptions(Option.SUPPRESS_EXCEPTIONS)); + final String id = dc.read(ID_JPATH); + if (StringUtils.isNotBlank(id)) { + + String prefix = StringUtils.substringBefore(id, "|"); + switch (prefix) { + case "10": + return parse(s, Datasource.class); + case "20": + return parse(s, Organization.class); + case "40": + return parse(s, Project.class); + case "50": + String resultType = dc.read("$.resulttype.classid"); + switch (resultType) { + case "publication": + return parse(s, Publication.class); + case "dataset": + return parse(s, eu.dnetlib.dhp.schema.oaf.Dataset.class); + case "software": + return parse(s, Software.class); + case "other": + return parse(s, OtherResearchProduct.class); + default: + throw new IllegalArgumentException(String.format("invalid resultType: '%s'", resultType)); + } + default: + throw new IllegalArgumentException(String.format("invalid id prefix: '%s'", prefix)); + } + } else { + throw new IllegalArgumentException(String.format("invalid oaf: '%s'", s)); + } + } + + private static OafEntity parse(String s, Class clazz) { + try { + return OBJECT_MAPPER.readValue(s, clazz); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static List listPaths(String inputPath, JavaSparkContext sc) { + return HdfsSupport + .listFiles(inputPath, sc.hadoopConfiguration()) + .stream() + .filter(f -> !f.equals("relation")) + .collect(Collectors.toList()); + } + +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPropagateRelation.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPropagateRelation.java index 699039c99..1cd1545cd 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPropagateRelation.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPropagateRelation.java @@ -3,6 +3,8 @@ package eu.dnetlib.dhp.oa.dedup; import static org.apache.spark.sql.functions.col; +import java.util.Objects; + import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.spark.SparkConf; @@ -28,7 +30,7 @@ public class SparkPropagateRelation extends AbstractSparkAction { SOURCE, TARGET } - public SparkPropagateRelation(ArgumentApplicationParser parser, SparkSession spark) throws Exception { + public SparkPropagateRelation(ArgumentApplicationParser parser, SparkSession spark) { super(parser, spark); } @@ -55,13 +57,13 @@ public class SparkPropagateRelation extends AbstractSparkAction { final String graphBasePath = parser.get("graphBasePath"); final String workingPath = parser.get("workingPath"); - final String dedupGraphPath = parser.get("dedupGraphPath"); + final String graphOutputPath = parser.get("graphOutputPath"); log.info("graphBasePath: '{}'", graphBasePath); log.info("workingPath: '{}'", workingPath); - log.info("dedupGraphPath: '{}'", dedupGraphPath); + log.info("graphOutputPath: '{}'", graphOutputPath); - final String outputRelationPath = DedupUtility.createEntityPath(dedupGraphPath, "relation"); + final String outputRelationPath = DedupUtility.createEntityPath(graphOutputPath, "relation"); removeOutputDir(spark, outputRelationPath); Dataset mergeRels = spark @@ -101,7 +103,8 @@ public class SparkPropagateRelation extends AbstractSparkAction { newRels .union(updated) .union(mergeRels) - .map((MapFunction) r -> r, Encoders.kryo(Relation.class))), + .map((MapFunction) r -> r, Encoders.kryo(Relation.class))) + .filter((FilterFunction) r -> !Objects.equals(r.getSource(), r.getTarget())), outputRelationPath, SaveMode.Overwrite); } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/consistency/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/consistency/oozie_app/workflow.xml index 926287032..71690a0ed 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/consistency/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/consistency/oozie_app/workflow.xml @@ -2,15 +2,15 @@ graphBasePath - the raw graph base path + the input graph base path workingPath path of the working directory - dedupGraphPath - path of the dedup graph + graphOutputPath + path of the output graph sparkDriverMemory @@ -91,116 +91,224 @@ --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} --conf spark.sql.shuffle.partitions=7680 - --i${graphBasePath} - --o${dedupGraphPath} - --w${workingPath} + --graphBasePath${graphBasePath} + --o${graphOutputPath} + --workingPath${workingPath} - + - - - - - - - - + + + yarn + cluster + group graph entities + eu.dnetlib.dhp.oa.dedup.GroupEntitiesSparkJob + dhp-dedup-openaire-${projectVersion}.jar + + --executor-cores=${sparkExecutorCores} + --executor-memory=${sparkExecutorMemory} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=7680 + + --graphInputPath${graphBasePath} + --outputPath${workingPath}/grouped_entities + + + + + + + + + + + + + - - - - - - -pb - ${graphBasePath}/datasource - ${dedupGraphPath}/datasource - - + + + yarn + cluster + Dispatch publications + eu.dnetlib.dhp.oa.dedup.DispatchEntitiesSparkJob + dhp-dedup-openaire-${projectVersion}.jar + + --executor-cores=${sparkExecutorCores} + --executor-memory=${sparkExecutorMemory} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=7680 + + --inputPath${workingPath}/grouped_entities + --outputPath${graphOutputPath}/datasource + --graphTableClassNameeu.dnetlib.dhp.schema.oaf.Datasource + + - - - - - - -pb - ${graphBasePath}/project - ${dedupGraphPath}/project - - + + + yarn + cluster + Dispatch project + eu.dnetlib.dhp.oa.dedup.DispatchEntitiesSparkJob + dhp-dedup-openaire-${projectVersion}.jar + + --executor-cores=${sparkExecutorCores} + --executor-memory=${sparkExecutorMemory} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=7680 + + --inputPath${workingPath}/grouped_entities + --outputPath${graphOutputPath}/project + --graphTableClassNameeu.dnetlib.dhp.schema.oaf.Project + + - - - - - - -pb - ${graphBasePath}/organization - ${dedupGraphPath}/organization - - + + + yarn + cluster + Dispatch organization + eu.dnetlib.dhp.oa.dedup.DispatchEntitiesSparkJob + dhp-dedup-openaire-${projectVersion}.jar + + --executor-cores=${sparkExecutorCores} + --executor-memory=${sparkExecutorMemory} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=7680 + + --inputPath${workingPath}/grouped_entities + --outputPath${graphOutputPath}/organization + --graphTableClassNameeu.dnetlib.dhp.schema.oaf.Organization + + - - - - - - -pb - ${graphBasePath}/publication - ${dedupGraphPath}/publication - - + + + yarn + cluster + Dispatch publication + eu.dnetlib.dhp.oa.dedup.DispatchEntitiesSparkJob + dhp-dedup-openaire-${projectVersion}.jar + + --executor-cores=${sparkExecutorCores} + --executor-memory=${sparkExecutorMemory} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=7680 + + --inputPath${workingPath}/grouped_entities + --outputPath${graphOutputPath}/publication + --graphTableClassNameeu.dnetlib.dhp.schema.oaf.Publication + + - - - - - - -pb - ${graphBasePath}/dataset - ${dedupGraphPath}/dataset - - + + + yarn + cluster + Dispatch dataset + eu.dnetlib.dhp.oa.dedup.DispatchEntitiesSparkJob + dhp-dedup-openaire-${projectVersion}.jar + + --executor-cores=${sparkExecutorCores} + --executor-memory=${sparkExecutorMemory} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=7680 + + --inputPath${workingPath}/grouped_entities + --outputPath${graphOutputPath}/dataset + --graphTableClassNameeu.dnetlib.dhp.schema.oaf.Dataset + + - - - - - - -pb - ${graphBasePath}/software - ${dedupGraphPath}/software - - + + + yarn + cluster + Dispatch software + eu.dnetlib.dhp.oa.dedup.DispatchEntitiesSparkJob + dhp-dedup-openaire-${projectVersion}.jar + + --executor-cores=${sparkExecutorCores} + --executor-memory=${sparkExecutorMemory} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=7680 + + --inputPath${workingPath}/grouped_entities + --outputPath${graphOutputPath}/software + --graphTableClassNameeu.dnetlib.dhp.schema.oaf.Software + + - - - - - - -pb - ${graphBasePath}/otherresearchproduct - ${dedupGraphPath}/otherresearchproduct - - + + + yarn + cluster + Dispatch otherresearchproduct + eu.dnetlib.dhp.oa.dedup.DispatchEntitiesSparkJob + dhp-dedup-openaire-${projectVersion}.jar + + --executor-cores=${sparkExecutorCores} + --executor-memory=${sparkExecutorMemory} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=7680 + + --inputPath${workingPath}/grouped_entities + --outputPath${graphOutputPath}/otherresearchproduct + --graphTableClassNameeu.dnetlib.dhp.schema.oaf.OtherResearchProduct + + - + \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/dispatch_entities_parameters.json b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/dispatch_entities_parameters.json new file mode 100644 index 000000000..aa8d2a7c2 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/dispatch_entities_parameters.json @@ -0,0 +1,26 @@ +[ + { + "paramName": "issm", + "paramLongName": "isSparkSessionManaged", + "paramDescription": "when true will stop SparkSession after job execution", + "paramRequired": false + }, + { + "paramName": "i", + "paramLongName": "inputPath", + "paramDescription": "the source path", + "paramRequired": true + }, + { + "paramName": "o", + "paramLongName": "outputPath", + "paramDescription": "path of the output graph", + "paramRequired": true + }, + { + "paramName": "c", + "paramLongName": "graphTableClassName", + "paramDescription": "the graph entity class name", + "paramRequired": true + } +] \ No newline at end of file diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/group_graph_entities_parameters.json b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/group_graph_entities_parameters.json similarity index 100% rename from dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/group_graph_entities_parameters.json rename to dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/group_graph_entities_parameters.json diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/propagateRelation_parameters.json b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/propagateRelation_parameters.json index 6a2a48746..e2a5281ae 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/propagateRelation_parameters.json +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/propagateRelation_parameters.json @@ -13,7 +13,7 @@ }, { "paramName": "o", - "paramLongName": "dedupGraphPath", + "paramLongName": "graphOutputPath", "paramDescription": "the path of the dedup graph", "paramRequired": true } From e208b037554bee8925ee7ed722d76954ccc7b9a0 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 25 Nov 2020 14:55:50 +0100 Subject: [PATCH 035/445] renamed workflow --- .../eu/dnetlib/dhp/oa/dedup/consistency/oozie_app/workflow.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/consistency/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/consistency/oozie_app/workflow.xml index 71690a0ed..6b5421c80 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/consistency/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/consistency/oozie_app/workflow.xml @@ -1,4 +1,4 @@ - + graphBasePath From 1372a4d1bf0793233ad6d94c6afa25fee814af6a Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 25 Nov 2020 16:05:51 +0100 Subject: [PATCH 036/445] fixed merging method --- .../dhp/schema/oaf/OafMapperUtils.java | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java index 6e5857505..20f8e7ba0 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java @@ -17,26 +17,30 @@ import eu.dnetlib.dhp.utils.DHPUtils; public class OafMapperUtils { - public static Oaf merge(final Oaf o1, final Oaf o2) { - if (ModelSupport.isSubClass(o1, OafEntity.class)) { - return mergeEntities((OafEntity) o1, (OafEntity) o2); - } else if (ModelSupport.isSubClass(o1, Relation.class)) { - ((Relation) o1).mergeFrom((Relation) o2); + public static Oaf merge(final Oaf left, final Oaf right) { + if (ModelSupport.isSubClass(left, OafEntity.class)) { + return mergeEntities((OafEntity) left, (OafEntity) right); + } else if (ModelSupport.isSubClass(left, Relation.class)) { + ((Relation) left).mergeFrom((Relation) right); + } else { + throw new RuntimeException("invalid Oaf type:" + left.getClass().getCanonicalName()); } - throw new RuntimeException("invalid Oaf type:" + o1.getClass().getCanonicalName()); + return left; } - public static OafEntity mergeEntities(OafEntity e1, OafEntity e2) { - if (ModelSupport.isSubClass(e1, Result.class)) { - return mergeResults((Result) e1, (Result) e2); - } else if (ModelSupport.isSubClass(e1, Datasource.class)) { - ((Datasource) e1).mergeFrom((Datasource) e2); - } else if (ModelSupport.isSubClass(e1, Organization.class)) { - ((Organization) e1).mergeFrom((Organization) e2); - } else if (ModelSupport.isSubClass(e1, Project.class)) { - ((Project) e1).mergeFrom((Project) e2); + public static OafEntity mergeEntities(OafEntity left, OafEntity right) { + if (ModelSupport.isSubClass(left, Result.class)) { + return mergeResults((Result) left, (Result) right); + } else if (ModelSupport.isSubClass(left, Datasource.class)) { + ((Datasource) left).mergeFrom((Datasource) right); + } else if (ModelSupport.isSubClass(left, Organization.class)) { + ((Organization) left).mergeFrom((Organization) right); + } else if (ModelSupport.isSubClass(left, Project.class)) { + ((Project) left).mergeFrom((Project) right); + } else { + throw new RuntimeException("invalid OafEntity subtype:" + left.getClass().getCanonicalName()); } - throw new RuntimeException("invalid OafEntity subtype:" + e1.getClass().getCanonicalName()); + return left; } public static Result mergeResults(Result r1, Result r2) { From c1b9a4045abc3538182fb55542991805459bc01e Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 26 Nov 2020 10:59:10 +0100 Subject: [PATCH 037/445] grouping of records will be performed by the dedup workflow --- .../dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java index cfd190670..2d32f62af 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java @@ -103,9 +103,6 @@ public class GenerateEntitiesApplication { } inputRdd - .mapToPair(oaf -> new Tuple2<>(ModelSupport.idFn().apply(oaf), oaf)) - .reduceByKey((o1, o2) -> OafMapperUtils.merge(o1, o2)) - .map(Tuple2::_2) .map( oaf -> oaf.getClass().getSimpleName().toLowerCase() + "|" From 76363a85128916d19ddabc1329d356553bb7e155 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 26 Nov 2020 11:03:12 +0100 Subject: [PATCH 038/445] SimpleDateFormat is not thread safe; improved error reporting in case of invalid dates --- .../dhp/oa/dedup/model/Identifier.java | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java index 39b26f919..f0d616600 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java @@ -4,10 +4,7 @@ package eu.dnetlib.dhp.oa.dedup.model; import java.io.Serializable; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.List; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; @@ -23,14 +20,16 @@ import eu.dnetlib.dhp.schema.oaf.utils.PidType; public class Identifier implements Serializable, Comparable { + public static final String DATE_FORMAT = "yyyy-MM-dd"; public static String CROSSREF_ID = "10|openaire____::081b82f96300b6a6e3d282bad31cb6e2"; public static String DATACITE_ID = "10|openaire____::9e3be59865b2c1c335d32dae2fe7b254"; public static String BASE_DATE = "2000-01-01"; - private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); - private T entity; + // cached date value + private Date date = null; + public static Identifier newInstance(T entity) { return new Identifier(entity); } @@ -48,17 +47,23 @@ public class Identifier implements Serializable, Comparable } public Date getDate() { - String date = BASE_DATE; - if (ModelSupport.isSubClass(getEntity(), Result.class)) { - Result result = (Result) getEntity(); - if (isWellformed(result.getDateofacceptance())) { - date = result.getDateofacceptance().getValue(); + if (Objects.nonNull(date)) { + return date; + } else { + String sDate = BASE_DATE; + if (ModelSupport.isSubClass(getEntity(), Result.class)) { + Result result = (Result) getEntity(); + if (isWellformed(result.getDateofacceptance())) { + sDate = result.getDateofacceptance().getValue(); + } + } + try { + this.date = new SimpleDateFormat(DATE_FORMAT).parse(sDate); + return date; + } catch (Throwable e) { + throw new RuntimeException( + String.format("cannot parse date: '%s' from record: '%s'", sDate, entity.getId())); } - } - try { - return sdf.parse(date); - } catch (ParseException e) { - return new Date(); } } @@ -117,10 +122,10 @@ public class Identifier implements Serializable, Comparable } if (this.getDate().compareTo(i.getDate()) == 0) {// same date - // the minus because we need to take the alphabetically lower id + // we need to take the alphabetically lower id return this.getOriginalID().compareTo(i.getOriginalID()); } else - // the minus is because we need to take the elder date + // we need to take the elder date return this.getDate().compareTo(i.getDate()); } else { return new PidComparator<>(getEntity()).compare(toSP(getPidType()), toSP(i.getPidType())); From 13eae4b31e79b6945b95d06954149d7b31463b76 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 26 Nov 2020 11:04:01 +0100 Subject: [PATCH 039/445] GroupEntitiesSparkJob must read all graph paths but relations --- .../java/eu/dnetlib/dhp/oa/dedup/GroupEntitiesSparkJob.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/GroupEntitiesSparkJob.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/GroupEntitiesSparkJob.java index 5835617fb..ec991cddc 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/GroupEntitiesSparkJob.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/GroupEntitiesSparkJob.java @@ -90,7 +90,7 @@ public class GroupEntitiesSparkJob { final JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); spark .read() - .textFile(toSeq(listPaths(inputPath, sc))) + .textFile(toSeq(listEntityPaths(inputPath, sc))) .map((MapFunction) s -> parseOaf(s), Encoders.kryo(OafEntity.class)) .filter((FilterFunction) e -> StringUtils.isNotBlank(ModelSupport.idFn().apply(e))) .groupByKey((MapFunction) oaf -> ModelSupport.idFn().apply(oaf), Encoders.STRING()) @@ -191,11 +191,11 @@ public class GroupEntitiesSparkJob { } } - private static List listPaths(String inputPath, JavaSparkContext sc) { + private static List listEntityPaths(String inputPath, JavaSparkContext sc) { return HdfsSupport .listFiles(inputPath, sc.hadoopConfiguration()) .stream() - .filter(f -> !f.equals("relation")) + .filter(f -> !f.toLowerCase().contains("relation")) .collect(Collectors.toList()); } From d0d5525d403aef8bf74856a0a29d810c874d1f1b Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 26 Nov 2020 11:04:17 +0100 Subject: [PATCH 040/445] minor changes --- .../eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java | 12 ++++++------ .../java/eu/dnetlib/dhp/oa/dedup/DatePicker.java | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java index 20f8e7ba0..5d6e7565d 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java @@ -43,13 +43,13 @@ public class OafMapperUtils { return left; } - public static Result mergeResults(Result r1, Result r2) { - if (new ResultTypeComparator().compare(r1, r2) < 0) { - r1.mergeFrom(r2); - return r1; + public static Result mergeResults(Result left, Result right) { + if (new ResultTypeComparator().compare(left, right) < 0) { + left.mergeFrom(right); + return left; } else { - r2.mergeFrom(r1); - return r2; + right.mergeFrom(left); + return right; } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DatePicker.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DatePicker.java index c2fe09a4d..688948728 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DatePicker.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DatePicker.java @@ -18,7 +18,7 @@ import eu.dnetlib.dhp.schema.oaf.Field; public class DatePicker { - public static final String DATE_PATTERN = "\\d{4}-\\d{2}-\\d{2}"; + public static final String DATE_PATTERN = "^\\d{4}-\\d{2}-\\d{2}$"; private static final String DATE_DEFAULT_SUFFIX = "01-01"; private static final int YEAR_LB = 1300; private static final int YEAR_UB = Year.now().getValue() + 5; From 5151850a19266bdae175b343d3158eb582b5ae3a Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 26 Nov 2020 13:08:36 +0100 Subject: [PATCH 041/445] CROSSREF and DATACITE constants moved in common ModelConstants --- .../dhp/schema/common/ModelConstants.java | 3 +++ .../dhp/oa/dedup/model/Identifier.java | 21 +++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java index d759f0d55..26149c62c 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java @@ -7,6 +7,9 @@ import eu.dnetlib.dhp.schema.oaf.Qualifier; public class ModelConstants { + public static String CROSSREF_ID = "10|openaire____::081b82f96300b6a6e3d282bad31cb6e2"; + public static String DATACITE_ID = "10|openaire____::9e3be59865b2c1c335d32dae2fe7b254"; + public static final String DNET_SUBJECT_TYPOLOGIES = "dnet:subject_classification_typologies"; public static final String DNET_RESULT_TYPOLOGIES = "dnet:result_typologies"; public static final String DNET_PUBLICATION_RESOURCE = "dnet:publication_resource"; diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java index f0d616600..cdc1fa24a 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java @@ -7,6 +7,7 @@ import java.text.SimpleDateFormat; import java.util.*; import java.util.stream.Collectors; +import eu.dnetlib.dhp.schema.common.ModelConstants; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.Sets; @@ -21,9 +22,7 @@ import eu.dnetlib.dhp.schema.oaf.utils.PidType; public class Identifier implements Serializable, Comparable { public static final String DATE_FORMAT = "yyyy-MM-dd"; - public static String CROSSREF_ID = "10|openaire____::081b82f96300b6a6e3d282bad31cb6e2"; - public static String DATACITE_ID = "10|openaire____::9e3be59865b2c1c335d32dae2fe7b254"; - public static String BASE_DATE = "2000-01-01"; + public static final String BASE_DATE = "2000-01-01"; private T entity; @@ -105,19 +104,19 @@ public class Identifier implements Serializable, Comparable if (this.getPidType().compareTo(i.getPidType()) == 0) { // same type if (getEntityType() == EntityType.publication) { - if (isFromDatasourceID(lKeys, CROSSREF_ID) - && !isFromDatasourceID(rKeys, CROSSREF_ID)) + if (isFromDatasourceID(lKeys, ModelConstants.CROSSREF_ID) + && !isFromDatasourceID(rKeys, ModelConstants.CROSSREF_ID)) return -1; - if (isFromDatasourceID(rKeys, CROSSREF_ID) - && !isFromDatasourceID(lKeys, CROSSREF_ID)) + if (isFromDatasourceID(rKeys, ModelConstants.CROSSREF_ID) + && !isFromDatasourceID(lKeys, ModelConstants.CROSSREF_ID)) return 1; } if (getEntityType() == EntityType.dataset) { - if (isFromDatasourceID(lKeys, DATACITE_ID) - && !isFromDatasourceID(rKeys, DATACITE_ID)) + if (isFromDatasourceID(lKeys, ModelConstants.CROSSREF_ID) + && !isFromDatasourceID(rKeys, ModelConstants.CROSSREF_ID)) return -1; - if (isFromDatasourceID(rKeys, DATACITE_ID) - && !isFromDatasourceID(lKeys, DATACITE_ID)) + if (isFromDatasourceID(rKeys, ModelConstants.CROSSREF_ID) + && !isFromDatasourceID(lKeys, ModelConstants.CROSSREF_ID)) return 1; } From fa66e5b6b8d79d139516fa0bb0758915186aca15 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 26 Nov 2020 13:09:19 +0100 Subject: [PATCH 042/445] ResultTypeComparator gives priority to Records collectedfrom Crossref --- .../dhp/schema/oaf/ResultTypeComparator.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ResultTypeComparator.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ResultTypeComparator.java index 6c11d1a85..eb54599ce 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ResultTypeComparator.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ResultTypeComparator.java @@ -2,8 +2,14 @@ package eu.dnetlib.dhp.schema.oaf; import java.util.Comparator; +import java.util.HashSet; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import com.google.common.collect.Sets; import eu.dnetlib.dhp.schema.common.ModelConstants; +import static eu.dnetlib.dhp.schema.common.ModelConstants.CROSSREF_ID; public class ResultTypeComparator implements Comparator { @@ -17,6 +23,16 @@ public class ResultTypeComparator implements Comparator { if (right == null) return -1; + HashSet lCf = getCollectedFromIds(left); + HashSet rCf = getCollectedFromIds(right); + + if (lCf.contains(CROSSREF_ID) && !rCf.contains(CROSSREF_ID)) { + return -1; + } + if (!lCf.contains(CROSSREF_ID) && rCf.contains(CROSSREF_ID)) { + return 1; + } + String lClass = left.getResulttype().getClassid(); String rClass = right.getResulttype().getClassid(); @@ -46,4 +62,12 @@ public class ResultTypeComparator implements Comparator { // Else (but unlikely), lexicographical ordering will do. return lClass.compareTo(rClass); } + + protected HashSet getCollectedFromIds(Result left) { + return Optional.ofNullable(left.getCollectedfrom()) + .map(cf -> cf.stream() + .map(c -> c.getKey()) + .collect(Collectors.toCollection(HashSet::new))) + .orElse(new HashSet<>()); + } } From 596a2a459def08c985c8b6612eb85769d1365e63 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Fri, 27 Nov 2020 12:01:11 +0100 Subject: [PATCH 043/445] added testing class for OafMapperUtils --- .../dhp/schema/oaf/OafMapperUtilsTest.java | 56 +++++++++++++++++++ .../eu/dnetlib/dhp/schema/oaf/dataset_1.json | 1 + .../eu/dnetlib/dhp/schema/oaf/dataset_2.json | 1 + .../dnetlib/dhp/schema/oaf/publication_1.json | 1 + .../dnetlib/dhp/schema/oaf/publication_2.json | 1 + 5 files changed, 60 insertions(+) create mode 100644 dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtilsTest.java create mode 100644 dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/dataset_1.json create mode 100644 dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/dataset_2.json create mode 100644 dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/publication_1.json create mode 100644 dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/publication_2.json diff --git a/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtilsTest.java b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtilsTest.java new file mode 100644 index 000000000..fa5720c95 --- /dev/null +++ b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtilsTest.java @@ -0,0 +1,56 @@ +package eu.dnetlib.dhp.schema.oaf; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.dnetlib.dhp.schema.common.ModelConstants; +import it.unimi.dsi.fastutil.Hash; +import org.apache.commons.io.IOUtils; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +public class OafMapperUtilsTest { + + private static ObjectMapper OBJECT_MAPPER = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + @Test + public void testMergePubs() throws IOException { + Publication p1 = read("publication_1.json", Publication.class); + Publication p2 = read("publication_2.json", Publication.class); + Dataset d1 = read("dataset_1.json", Dataset.class); + Dataset d2 = read("dataset_2.json", Dataset.class); + + assertEquals(p1.getCollectedfrom().size(), 1); + assertEquals(p1.getCollectedfrom().get(0).getKey(), ModelConstants.CROSSREF_ID); + assertEquals(d2.getCollectedfrom().size(), 1); + assertFalse(cfId(d2.getCollectedfrom()).contains(ModelConstants.CROSSREF_ID)); + + assertTrue(OafMapperUtils.mergeResults(p1, d2).getResulttype().getClassid().equals(ModelConstants.PUBLICATION_RESULTTYPE_CLASSID)); + + assertEquals(p2.getCollectedfrom().size(), 1); + assertFalse(cfId(p2.getCollectedfrom()).contains(ModelConstants.CROSSREF_ID)); + assertEquals(d1.getCollectedfrom().size(), 1); + assertTrue(cfId(d1.getCollectedfrom()).contains(ModelConstants.CROSSREF_ID)); + + assertTrue(OafMapperUtils.mergeResults(p2, d1).getResulttype().getClassid().equals(ModelConstants.DATASET_RESULTTYPE_CLASSID)); + } + + @NotNull + protected HashSet cfId(List collectedfrom) { + return collectedfrom.stream().map(c -> c.getKey()).collect(Collectors.toCollection(HashSet::new)); + } + + protected T read(String filename, Class clazz ) throws IOException { + final String json = IOUtils.toString(getClass().getResourceAsStream(filename)); + return OBJECT_MAPPER.readValue(json, clazz); + } + +} diff --git a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/dataset_1.json b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/dataset_1.json new file mode 100644 index 000000000..e38c4d1cc --- /dev/null +++ b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/dataset_1.json @@ -0,0 +1 @@ +{"id":"50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1g", "resuttype" : { "classid" : "dataset" }, "pid":[{"qualifier":{"classid":"doi"},"value":"10.1016/j.cmet.2011.03.013"},{"qualifier":{"classid":"urn"},"value":"urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2"},{"qualifier":{"classid":"scp-number"},"value":"79953761260"},{"qualifier":{"classid":"pmc"},"value":"21459329"}], "collectedfrom" : [ { "key" : "10|openaire____::081b82f96300b6a6e3d282bad31cb6e2", "value" : "Crossref"} ]} \ No newline at end of file diff --git a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/dataset_2.json b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/dataset_2.json new file mode 100644 index 000000000..52e4e126a --- /dev/null +++ b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/dataset_2.json @@ -0,0 +1 @@ +{"id":"50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1g", "resuttype" : { "classid" : "dataset" }, "pid":[{"qualifier":{"classid":"doi"},"value":"10.1016/j.cmet.2011.03.013"},{"qualifier":{"classid":"urn"},"value":"urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2"},{"qualifier":{"classid":"scp-number"},"value":"79953761260"},{"qualifier":{"classid":"pmc"},"value":"21459329"}], "collectedfrom" : [ { "key" : "10|openaire____::081b82f96300b6a6e3d282bad31cb6e3", "value" : "Repository B"} ]} \ No newline at end of file diff --git a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/publication_1.json b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/publication_1.json new file mode 100644 index 000000000..704c5ad4d --- /dev/null +++ b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/publication_1.json @@ -0,0 +1 @@ +{"id":"50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f", "resuttype" : { "classid" : "publication" }, "pid":[{"qualifier":{"classid":"doi"},"value":"10.1016/j.cmet.2011.03.013"},{"qualifier":{"classid":"urn"},"value":"urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2"},{"qualifier":{"classid":"scp-number"},"value":"79953761260"},{"qualifier":{"classid":"pmc"},"value":"21459329"}], "collectedfrom" : [ { "key" : "10|openaire____::081b82f96300b6a6e3d282bad31cb6e2", "value" : "Crossref"} ]} \ No newline at end of file diff --git a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/publication_2.json b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/publication_2.json new file mode 100644 index 000000000..a1744e84e --- /dev/null +++ b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/publication_2.json @@ -0,0 +1 @@ +{"id":"50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f", "resuttype" : { "classid" : "publication" }, "pid":[{"qualifier":{"classid":"doi"},"value":"10.1016/j.cmet.2011.03.013"},{"qualifier":{"classid":"urn"},"value":"urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2"},{"qualifier":{"classid":"scp-number"},"value":"79953761260"},{"qualifier":{"classid":"pmc"},"value":"21459329"}], "collectedfrom" : [ { "key" : "10|openaire____::081b82f96300b6a6e3d282bad31cb6e3", "value" : "Repository A"} ]} \ No newline at end of file From 758d27745d9e63b994e700896cd817eb74960446 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Fri, 27 Nov 2020 16:07:24 +0100 Subject: [PATCH 044/445] cleaning tab characters from text fields --- .../dhp/schema/oaf/CleaningFunctions.java | 16 ++-- .../dhp/schema/oaf/ResultTypeComparator.java | 17 ++-- .../dhp/schema/oaf/OafMapperUtilsTest.java | 85 +++++++++++-------- .../dhp/oa/dedup/model/Identifier.java | 2 +- 4 files changed, 69 insertions(+), 51 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index 0fae82a8a..5f191e9a9 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -15,7 +15,7 @@ public class CleaningFunctions { public static final String DOI_URL_PREFIX_REGEX = "(^http(s?):\\/\\/)(((dx\\.)?doi\\.org)|(handle\\.test\\.datacite\\.org))\\/"; public static final String ORCID_PREFIX_REGEX = "^http(s?):\\/\\/orcid\\.org\\/"; - public static final String NEWLINES = "(?:\\n|\\r)"; + public static final String CLEANING_REGEX = "(?:\\n|\\r|\\t)"; public static final Set PID_BLACKLIST = new HashSet<>(); @@ -109,7 +109,7 @@ public class CleaningFunctions { .filter(sp -> StringUtils.isNotBlank(sp.getValue())) .filter(sp -> Objects.nonNull(sp.getQualifier())) .filter(sp -> StringUtils.isNotBlank(sp.getQualifier().getClassid())) - .map(CleaningFunctions::removeNewLines) + .map(CleaningFunctions::cleanValue) .collect(Collectors.toList())); } if (Objects.nonNull(r.getTitle())) { @@ -120,7 +120,7 @@ public class CleaningFunctions { .stream() .filter(Objects::nonNull) .filter(sp -> StringUtils.isNotBlank(sp.getValue())) - .map(CleaningFunctions::removeNewLines) + .map(CleaningFunctions::cleanValue) .collect(Collectors.toList())); } if (Objects.nonNull(r.getDescription())) { @@ -131,7 +131,7 @@ public class CleaningFunctions { .stream() .filter(Objects::nonNull) .filter(sp -> StringUtils.isNotBlank(sp.getValue())) - .map(CleaningFunctions::removeNewLines) + .map(CleaningFunctions::cleanValue) .collect(Collectors.toList())); } if (Objects.nonNull(r.getPid())) { @@ -228,13 +228,13 @@ public class CleaningFunctions { return value; } - protected static StructuredProperty removeNewLines(StructuredProperty s) { - s.setValue(s.getValue().replaceAll(NEWLINES, " ")); + protected static StructuredProperty cleanValue(StructuredProperty s) { + s.setValue(s.getValue().replaceAll(CLEANING_REGEX, " ")); return s; } - protected static Field removeNewLines(Field s) { - s.setValue(s.getValue().replaceAll(NEWLINES, " ")); + protected static Field cleanValue(Field s) { + s.setValue(s.getValue().replaceAll(CLEANING_REGEX, " ")); return s; } diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ResultTypeComparator.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ResultTypeComparator.java index eb54599ce..089d71a0c 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ResultTypeComparator.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/ResultTypeComparator.java @@ -1,6 +1,8 @@ package eu.dnetlib.dhp.schema.oaf; +import static eu.dnetlib.dhp.schema.common.ModelConstants.CROSSREF_ID; + import java.util.Comparator; import java.util.HashSet; import java.util.Optional; @@ -8,8 +10,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import com.google.common.collect.Sets; + import eu.dnetlib.dhp.schema.common.ModelConstants; -import static eu.dnetlib.dhp.schema.common.ModelConstants.CROSSREF_ID; public class ResultTypeComparator implements Comparator { @@ -64,10 +66,13 @@ public class ResultTypeComparator implements Comparator { } protected HashSet getCollectedFromIds(Result left) { - return Optional.ofNullable(left.getCollectedfrom()) - .map(cf -> cf.stream() - .map(c -> c.getKey()) - .collect(Collectors.toCollection(HashSet::new))) - .orElse(new HashSet<>()); + return Optional + .ofNullable(left.getCollectedfrom()) + .map( + cf -> cf + .stream() + .map(c -> c.getKey()) + .collect(Collectors.toCollection(HashSet::new))) + .orElse(new HashSet<>()); } } diff --git a/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtilsTest.java b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtilsTest.java index fa5720c95..93840d534 100644 --- a/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtilsTest.java +++ b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtilsTest.java @@ -1,56 +1,69 @@ + package eu.dnetlib.dhp.schema.oaf; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import eu.dnetlib.dhp.schema.common.ModelConstants; -import it.unimi.dsi.fastutil.Hash; -import org.apache.commons.io.IOUtils; -import org.jetbrains.annotations.NotNull; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.*; +import org.apache.commons.io.IOUtils; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.dnetlib.dhp.schema.common.ModelConstants; +import it.unimi.dsi.fastutil.Hash; public class OafMapperUtilsTest { - private static ObjectMapper OBJECT_MAPPER = new ObjectMapper() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + private static ObjectMapper OBJECT_MAPPER = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - @Test - public void testMergePubs() throws IOException { - Publication p1 = read("publication_1.json", Publication.class); - Publication p2 = read("publication_2.json", Publication.class); - Dataset d1 = read("dataset_1.json", Dataset.class); - Dataset d2 = read("dataset_2.json", Dataset.class); + @Test + public void testMergePubs() throws IOException { + Publication p1 = read("publication_1.json", Publication.class); + Publication p2 = read("publication_2.json", Publication.class); + Dataset d1 = read("dataset_1.json", Dataset.class); + Dataset d2 = read("dataset_2.json", Dataset.class); - assertEquals(p1.getCollectedfrom().size(), 1); - assertEquals(p1.getCollectedfrom().get(0).getKey(), ModelConstants.CROSSREF_ID); - assertEquals(d2.getCollectedfrom().size(), 1); - assertFalse(cfId(d2.getCollectedfrom()).contains(ModelConstants.CROSSREF_ID)); + assertEquals(p1.getCollectedfrom().size(), 1); + assertEquals(p1.getCollectedfrom().get(0).getKey(), ModelConstants.CROSSREF_ID); + assertEquals(d2.getCollectedfrom().size(), 1); + assertFalse(cfId(d2.getCollectedfrom()).contains(ModelConstants.CROSSREF_ID)); - assertTrue(OafMapperUtils.mergeResults(p1, d2).getResulttype().getClassid().equals(ModelConstants.PUBLICATION_RESULTTYPE_CLASSID)); + assertTrue( + OafMapperUtils + .mergeResults(p1, d2) + .getResulttype() + .getClassid() + .equals(ModelConstants.PUBLICATION_RESULTTYPE_CLASSID)); - assertEquals(p2.getCollectedfrom().size(), 1); - assertFalse(cfId(p2.getCollectedfrom()).contains(ModelConstants.CROSSREF_ID)); - assertEquals(d1.getCollectedfrom().size(), 1); - assertTrue(cfId(d1.getCollectedfrom()).contains(ModelConstants.CROSSREF_ID)); + assertEquals(p2.getCollectedfrom().size(), 1); + assertFalse(cfId(p2.getCollectedfrom()).contains(ModelConstants.CROSSREF_ID)); + assertEquals(d1.getCollectedfrom().size(), 1); + assertTrue(cfId(d1.getCollectedfrom()).contains(ModelConstants.CROSSREF_ID)); - assertTrue(OafMapperUtils.mergeResults(p2, d1).getResulttype().getClassid().equals(ModelConstants.DATASET_RESULTTYPE_CLASSID)); - } + assertTrue( + OafMapperUtils + .mergeResults(p2, d1) + .getResulttype() + .getClassid() + .equals(ModelConstants.DATASET_RESULTTYPE_CLASSID)); + } - @NotNull - protected HashSet cfId(List collectedfrom) { - return collectedfrom.stream().map(c -> c.getKey()).collect(Collectors.toCollection(HashSet::new)); - } + @NotNull + protected HashSet cfId(List collectedfrom) { + return collectedfrom.stream().map(c -> c.getKey()).collect(Collectors.toCollection(HashSet::new)); + } - protected T read(String filename, Class clazz ) throws IOException { - final String json = IOUtils.toString(getClass().getResourceAsStream(filename)); - return OBJECT_MAPPER.readValue(json, clazz); - } + protected T read(String filename, Class clazz) throws IOException { + final String json = IOUtils.toString(getClass().getResourceAsStream(filename)); + return OBJECT_MAPPER.readValue(json, clazz); + } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java index cdc1fa24a..18f4f9b84 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java @@ -7,13 +7,13 @@ import java.text.SimpleDateFormat; import java.util.*; import java.util.stream.Collectors; -import eu.dnetlib.dhp.schema.common.ModelConstants; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.Sets; import eu.dnetlib.dhp.oa.dedup.DatePicker; import eu.dnetlib.dhp.schema.common.EntityType; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.dhp.schema.oaf.utils.PidComparator; From 2c407e775ed1401a951737593cc2263bfd8b8d71 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 30 Nov 2020 12:00:38 +0100 Subject: [PATCH 045/445] GenerateEntitiesApplication can be configured to hash the id value or not --- .../schema/oaf/utils/IdentifierFactory.java | 36 ++++++++++--------- .../oaf/utils/IdentifierFactoryTest.java | 35 +++++++++++++----- .../raw/AbstractMdRecordToOafMapper.java | 8 +++-- .../raw/GenerateEntitiesApplication.java | 22 ++++++++---- .../dhp/oa/graph/raw/OafToOafMapper.java | 4 +-- .../dhp/oa/graph/raw/OdfToOafMapper.java | 4 +-- .../raw/GenerateEntitiesApplicationTest.java | 2 +- .../dnetlib/dhp/oa/graph/raw/MappersTest.java | 20 +++++------ 8 files changed, 82 insertions(+), 49 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 01bb92bf6..7acc021b4 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -8,7 +8,7 @@ import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import eu.dnetlib.dhp.schema.oaf.CleaningFunctions; import eu.dnetlib.dhp.schema.oaf.OafEntity; @@ -31,17 +31,16 @@ public class IdentifierFactory implements Serializable { "(^10\\.1207\\/[a-zA-Z0-9_]+\\&[0-9]+_[0-9]+$)"; public static final int ID_PREFIX_LEN = 12; - public static final String NONE = "none"; /** * Creates an identifier from the most relevant PID (if available) in the given entity T. Returns entity.id * when no PID is available * @param entity the entity providing PIDs and a default ID. * @param the specific entity type. Currently Organization and Result subclasses are supported. + * @param md5 indicates whether should hash the PID value or not. * @return an identifier from the most relevant PID, entity.id otherwise */ - public static String createIdentifier(T entity) { - + public static String createIdentifier(T entity, boolean md5) { if (Objects.isNull(entity.getPid()) || entity.getPid().isEmpty()) { return entity.getId(); } @@ -69,15 +68,27 @@ public class IdentifierFactory implements Serializable { .stream() .sorted(new PidValueComparator()) .findFirst() - .map(s -> idFromPid(entity, s)) + .map(s -> idFromPid(entity, s, md5)) .orElseGet(entity::getId)) .orElseGet(entity::getId)) .orElseGet(entity::getId); } + /** + * @see {@link IdentifierFactory#createIdentifier(OafEntity, boolean)} + */ + public static String createIdentifier(T entity) { + + return createIdentifier(entity, true); + } + protected static boolean pidFilter(StructuredProperty s) { if (Objects.isNull(s.getQualifier()) || - StringUtils.isBlank(StringUtils.trim(s.getValue()))) { + StringUtils.isBlank(s.getValue()) || + StringUtils.isBlank(s.getValue().replaceAll("(?:\\n|\\r|\\t|\\s)", ""))) { + return false; + } + if (CleaningFunctions.PID_BLACKLIST.contains(StringUtils.trim(s.getValue().toLowerCase()))) { return false; } try { @@ -93,21 +104,14 @@ public class IdentifierFactory implements Serializable { } } - private static String verifyIdSyntax(String s) { - if (StringUtils.isBlank(s) || !s.matches(ID_REGEX)) { - throw new RuntimeException(String.format("malformed id: '%s'", s)); - } else { - return s; - } - } - - private static String idFromPid(T entity, StructuredProperty s) { + private static String idFromPid(T entity, StructuredProperty s, boolean md5) { + final String value = CleaningFunctions.normalizePidValue(s).getValue(); return new StringBuilder() .append(StringUtils.substringBefore(entity.getId(), ID_PREFIX_SEPARATOR)) .append(ID_PREFIX_SEPARATOR) .append(createPrefix(s.getQualifier().getClassid())) .append(ID_SEPARATOR) - .append(DHPUtils.md5(CleaningFunctions.normalizePidValue(s).getValue())) + .append(md5 ? DHPUtils.md5(value) : value) .toString(); } diff --git a/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java index 2b34a46ca..17f172a42 100644 --- a/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java +++ b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java @@ -22,24 +22,41 @@ public class IdentifierFactoryTest { @Test public void testCreateIdentifierForPublication() throws IOException { - verifyIdentifier("publication_doi1.json", "50|doi_________::" + DHPUtils.md5("10.1016/j.cmet.2011.03.013")); - verifyIdentifier("publication_doi2.json", "50|doi_________::" + DHPUtils.md5("10.1016/j.cmet.2010.03.013")); - verifyIdentifier("publication_pmc1.json", "50|pmc_________::" + DHPUtils.md5("21459329")); + verifyIdentifier( + "publication_doi1.json", "50|doi_________::" + DHPUtils.md5("10.1016/j.cmet.2011.03.013"), true); + verifyIdentifier( + "publication_doi2.json", "50|doi_________::" + DHPUtils.md5("10.1016/j.cmet.2010.03.013"), true); + verifyIdentifier("publication_pmc1.json", "50|pmc_________::" + DHPUtils.md5("21459329"), true); verifyIdentifier( "publication_urn1.json", - "50|urn_________::" + DHPUtils.md5("urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2")); + "50|urn_________::" + DHPUtils.md5("urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2"), true); final String defaultID = "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f"; - verifyIdentifier("publication_3.json", defaultID); - verifyIdentifier("publication_4.json", defaultID); - verifyIdentifier("publication_5.json", defaultID); + verifyIdentifier("publication_3.json", defaultID, true); + verifyIdentifier("publication_4.json", defaultID, true); + verifyIdentifier("publication_5.json", defaultID, true); } - protected void verifyIdentifier(String filename, String expectedID) throws IOException { + @Test + public void testCreateIdentifierForPublicationNoHash() throws IOException { + + verifyIdentifier("publication_doi1.json", "50|doi_________::10.1016/j.cmet.2011.03.013", false); + verifyIdentifier("publication_doi2.json", "50|doi_________::10.1016/j.cmet.2010.03.013", false); + verifyIdentifier("publication_pmc1.json", "50|pmc_________::21459329", false); + verifyIdentifier( + "publication_urn1.json", "50|urn_________::urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2", false); + + final String defaultID = "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f"; + verifyIdentifier("publication_3.json", defaultID, false); + verifyIdentifier("publication_4.json", defaultID, false); + verifyIdentifier("publication_5.json", defaultID, false); + } + + protected void verifyIdentifier(String filename, String expectedID, boolean md5) throws IOException { final String json = IOUtils.toString(getClass().getResourceAsStream(filename)); final Publication pub = OBJECT_MAPPER.readValue(json, Publication.class); - String id = IdentifierFactory.createIdentifier(pub); + String id = IdentifierFactory.createIdentifier(pub, md5); assertNotNull(id); assertEquals(expectedID, id); diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java index 9db56198f..00a7f3a92 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java @@ -26,6 +26,8 @@ public abstract class AbstractMdRecordToOafMapper { private final boolean invisible; + private final boolean shouldHashId; + protected static final String DATACITE_SCHEMA_KERNEL_4 = "http://datacite.org/schema/kernel-4"; protected static final String DATACITE_SCHEMA_KERNEL_4_SLASH = "http://datacite.org/schema/kernel-4/"; protected static final String DATACITE_SCHEMA_KERNEL_3 = "http://datacite.org/schema/kernel-3"; @@ -50,9 +52,11 @@ public abstract class AbstractMdRecordToOafMapper { protected static final Qualifier MAIN_TITLE_QUALIFIER = qualifier( "main title", "main title", "dnet:dataCite_title", "dnet:dataCite_title"); - protected AbstractMdRecordToOafMapper(final VocabularyGroup vocs, final boolean invisible) { + protected AbstractMdRecordToOafMapper(final VocabularyGroup vocs, final boolean invisible, + final boolean shouldHashId) { this.vocs = vocs; this.invisible = invisible; + this.shouldHashId = shouldHashId; } public List processMdRecord(final String xml) { @@ -137,7 +141,7 @@ public abstract class AbstractMdRecordToOafMapper { final long lastUpdateTimestamp) { final OafEntity entity = createEntity(doc, type, instances, collectedFrom, info, lastUpdateTimestamp); - final String id = IdentifierFactory.createIdentifier(entity); + final String id = IdentifierFactory.createIdentifier(entity, shouldHashId); if (!id.equals(entity.getId())) { entity.getOriginalId().add(entity.getId()); entity.setId(id); diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java index 2d32f62af..40020427a 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java @@ -64,13 +64,19 @@ public class GenerateEntitiesApplication { final String isLookupUrl = parser.get("isLookupUrl"); log.info("isLookupUrl: {}", isLookupUrl); + final boolean shouldHashId = Optional + .ofNullable(parser.get("shouldHashId")) + .map(Boolean::valueOf) + .orElse(true); + log.info("shouldHashId: {}", shouldHashId); + final ISLookUpService isLookupService = ISLookupClientFactory.getLookUpService(isLookupUrl); final VocabularyGroup vocs = VocabularyGroup.loadVocsFromIS(isLookupService); final SparkConf conf = new SparkConf(); runWithSparkSession(conf, isSparkSessionManaged, spark -> { HdfsSupport.remove(targetPath, spark.sparkContext().hadoopConfiguration()); - generateEntities(spark, vocs, sourcePaths, targetPath); + generateEntities(spark, vocs, sourcePaths, targetPath, shouldHashId); }); } @@ -78,7 +84,8 @@ public class GenerateEntitiesApplication { final SparkSession spark, final VocabularyGroup vocs, final String sourcePaths, - final String targetPath) { + final String targetPath, + final boolean shouldHashId) { final JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); final List existingSourcePaths = Arrays @@ -97,7 +104,7 @@ public class GenerateEntitiesApplication { sc .sequenceFile(sp, Text.class, Text.class) .map(k -> new Tuple2<>(k._1().toString(), k._2().toString())) - .map(k -> convertToListOaf(k._1(), k._2(), vocs)) + .map(k -> convertToListOaf(k._1(), k._2(), shouldHashId, vocs)) .filter(Objects::nonNull) .flatMap(list -> list.iterator())); } @@ -113,20 +120,21 @@ public class GenerateEntitiesApplication { private static List convertToListOaf( final String id, final String s, + final boolean shouldHashId, final VocabularyGroup vocs) { final String type = StringUtils.substringAfter(id, ":"); switch (type.toLowerCase()) { case "oaf-store-cleaned": case "oaf-store-claim": - return new OafToOafMapper(vocs, false).processMdRecord(s); + return new OafToOafMapper(vocs, false, shouldHashId).processMdRecord(s); case "odf-store-cleaned": case "odf-store-claim": - return new OdfToOafMapper(vocs, false).processMdRecord(s); + return new OdfToOafMapper(vocs, false, shouldHashId).processMdRecord(s); case "oaf-store-intersection": - return new OafToOafMapper(vocs, true).processMdRecord(s); + return new OafToOafMapper(vocs, true, shouldHashId).processMdRecord(s); case "odf-store-intersection": - return new OdfToOafMapper(vocs, true).processMdRecord(s); + return new OdfToOafMapper(vocs, true, shouldHashId).processMdRecord(s); case "datasource": return Arrays.asList(convertFromJson(s, Datasource.class)); case "organization": diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java index e28e8bd3c..50208a079 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java @@ -22,8 +22,8 @@ import eu.dnetlib.dhp.schema.oaf.CleaningFunctions; public class OafToOafMapper extends AbstractMdRecordToOafMapper { - public OafToOafMapper(final VocabularyGroup vocs, final boolean invisible) { - super(vocs, invisible); + public OafToOafMapper(final VocabularyGroup vocs, final boolean invisible, final boolean shouldHashId) { + super(vocs, invisible, shouldHashId); } @Override diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java index 6ceaa405a..88a29fdd7 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java @@ -19,8 +19,8 @@ public class OdfToOafMapper extends AbstractMdRecordToOafMapper { public static final String HTTP_DX_DOI_PREIFX = "http://dx.doi.org/"; - public OdfToOafMapper(final VocabularyGroup vocs, final boolean invisible) { - super(vocs, invisible); + public OdfToOafMapper(final VocabularyGroup vocs, final boolean invisible, final boolean shouldHashId) { + super(vocs, invisible, shouldHashId); } @Override diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java index 705f1dddb..4c1b0a739 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java @@ -77,7 +77,7 @@ public class GenerateEntitiesApplicationTest { protected Result getResult(String xmlFileName, Class clazz) throws IOException { final String xml = IOUtils.toString(getClass().getResourceAsStream(xmlFileName)); - return new OdfToOafMapper(vocs, false) + return new OdfToOafMapper(vocs, false, true) .processMdRecord(xml) .stream() .filter(s -> clazz.isAssignableFrom(s.getClass())) diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java index 4e4a21fa9..5fc3cb5d0 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java @@ -52,7 +52,7 @@ public class MappersTest { final String xml = IOUtils.toString(getClass().getResourceAsStream("oaf_record.xml")); - final List list = new OafToOafMapper(vocs, false).processMdRecord(xml); + final List list = new OafToOafMapper(vocs, false, true).processMdRecord(xml); assertEquals(3, list.size()); assertTrue(list.get(0) instanceof Publication); @@ -131,7 +131,7 @@ public class MappersTest { final String xml = IOUtils.toString(getClass().getResourceAsStream("oaf_record.xml")); - final List list = new OafToOafMapper(vocs, true).processMdRecord(xml); + final List list = new OafToOafMapper(vocs, true, true).processMdRecord(xml); assertTrue(list.size() > 0); assertTrue(list.get(0) instanceof Publication); @@ -146,7 +146,7 @@ public class MappersTest { void testDataset() throws IOException { final String xml = IOUtils.toString(getClass().getResourceAsStream("odf_dataset.xml")); - final List list = new OdfToOafMapper(vocs, false).processMdRecord(xml); + final List list = new OdfToOafMapper(vocs, false, true).processMdRecord(xml); assertEquals(3, list.size()); assertTrue(list.get(0) instanceof Dataset); @@ -240,7 +240,7 @@ public class MappersTest { void testSoftware() throws IOException { final String xml = IOUtils.toString(getClass().getResourceAsStream("odf_software.xml")); - final List list = new OdfToOafMapper(vocs, false).processMdRecord(xml); + final List list = new OdfToOafMapper(vocs, false, true).processMdRecord(xml); assertEquals(1, list.size()); assertTrue(list.get(0) instanceof Software); @@ -259,7 +259,7 @@ public class MappersTest { void testDataset_2() throws IOException { final String xml = IOUtils.toString(getClass().getResourceAsStream("odf_dataset_2.xml")); - final List list = new OdfToOafMapper(vocs, false).processMdRecord(xml); + final List list = new OdfToOafMapper(vocs, false, true).processMdRecord(xml); System.out.println("***************"); System.out.println(new ObjectMapper().writeValueAsString(list)); @@ -269,7 +269,7 @@ public class MappersTest { @Test void testClaimDedup() throws IOException { final String xml = IOUtils.toString(getClass().getResourceAsStream("oaf_claim_dedup.xml")); - final List list = new OafToOafMapper(vocs, false).processMdRecord(xml); + final List list = new OafToOafMapper(vocs, false, true).processMdRecord(xml); System.out.println("***************"); System.out.println(new ObjectMapper().writeValueAsString(list)); @@ -279,7 +279,7 @@ public class MappersTest { @Test void testNakala() throws IOException { final String xml = IOUtils.toString(getClass().getResourceAsStream("odf_nakala.xml")); - final List list = new OdfToOafMapper(vocs, false).processMdRecord(xml); + final List list = new OdfToOafMapper(vocs, false, true).processMdRecord(xml); System.out.println("***************"); System.out.println(new ObjectMapper().writeValueAsString(list)); @@ -303,7 +303,7 @@ public class MappersTest { @Test void testClaimFromCrossref() throws IOException { final String xml = IOUtils.toString(getClass().getResourceAsStream("oaf_claim_crossref.xml")); - final List list = new OafToOafMapper(vocs, false).processMdRecord(xml); + final List list = new OafToOafMapper(vocs, false, true).processMdRecord(xml); System.out.println("***************"); System.out.println(new ObjectMapper().writeValueAsString(list)); @@ -319,7 +319,7 @@ public class MappersTest { @Test void testODFRecord() throws IOException { final String xml = IOUtils.toString(getClass().getResourceAsStream("odf_record.xml")); - final List list = new OdfToOafMapper(vocs, false).processMdRecord(xml); + final List list = new OdfToOafMapper(vocs, false, true).processMdRecord(xml); System.out.println("***************"); System.out.println(new ObjectMapper().writeValueAsString(list)); System.out.println("***************"); @@ -333,7 +333,7 @@ public class MappersTest { @Test void testTextGrid() throws IOException { final String xml = IOUtils.toString(getClass().getResourceAsStream("textgrid.xml")); - final List list = new OdfToOafMapper(vocs, false).processMdRecord(xml); + final List list = new OdfToOafMapper(vocs, false, true).processMdRecord(xml); System.out.println("***************"); System.out.println(new ObjectMapper().writeValueAsString(list)); From 349e7246aa77db4baeeb7f152c8bfc882d0edd4d Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 30 Nov 2020 16:52:40 +0100 Subject: [PATCH 046/445] do not consider NCID, GBIF as PIDs candidate for the ID creation --- .../schema/oaf/utils/IdentifierFactory.java | 18 ++++++++---------- .../oaf/utils/OrganizationPidComparator.java | 4 ++-- .../dnetlib/dhp/schema/oaf/utils/PidType.java | 2 +- .../schema/oaf/utils/ResultPidComparator.java | 18 ++++-------------- 4 files changed, 15 insertions(+), 27 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 7acc021b4..5c3a4c37c 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -91,16 +91,14 @@ public class IdentifierFactory implements Serializable { if (CleaningFunctions.PID_BLACKLIST.contains(StringUtils.trim(s.getValue().toLowerCase()))) { return false; } - try { - switch (PidType.valueOf(s.getQualifier().getClassid())) { - case doi: - final String doi = StringUtils.trim(StringUtils.lowerCase(s.getValue())); - return doi.matches(DOI_REGEX); - default: - return true; - } - } catch (IllegalArgumentException e) { - return false; + switch (PidType.tryValueOf(s.getQualifier().getClassid())) { + case doi: + final String doi = StringUtils.trim(StringUtils.lowerCase(s.getValue())); + return doi.matches(DOI_REGEX); + case original: + return false; + default: + return true; } } diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java index 8cca517f9..57285fb82 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java @@ -10,8 +10,8 @@ public class OrganizationPidComparator implements Comparator @Override public int compare(StructuredProperty left, StructuredProperty right) { - PidType lClass = PidType.valueOf(left.getQualifier().getClassid()); - PidType rClass = PidType.valueOf(right.getQualifier().getClassid()); + PidType lClass = PidType.tryValueOf(left.getQualifier().getClassid()); + PidType rClass = PidType.tryValueOf(right.getQualifier().getClassid()); if (lClass.equals(PidType.GRID)) return -1; diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidType.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidType.java index 9a0196ccc..62f682026 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidType.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidType.java @@ -6,7 +6,7 @@ import org.apache.commons.lang3.EnumUtils; public enum PidType { // Result - doi, pmid, pmc, handle, arXiv, NCID, GBIF, nct, pdb, + doi, pmid, pmc, handle, arXiv, nct, pdb, // Organization GRID, mag_id, urn, diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/ResultPidComparator.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/ResultPidComparator.java index 38c7743a2..e51c4801f 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/ResultPidComparator.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/ResultPidComparator.java @@ -10,8 +10,8 @@ public class ResultPidComparator implements Comparator { @Override public int compare(StructuredProperty left, StructuredProperty right) { - PidType lClass = PidType.valueOf(left.getQualifier().getClassid()); - PidType rClass = PidType.valueOf(right.getQualifier().getClassid()); + PidType lClass = PidType.tryValueOf(left.getQualifier().getClassid()); + PidType rClass = PidType.tryValueOf(right.getQualifier().getClassid()); if (lClass.equals(PidType.doi)) return -1; @@ -38,24 +38,14 @@ public class ResultPidComparator implements Comparator { if (rClass.equals(PidType.arXiv)) return 1; - if (lClass.equals(PidType.NCID)) - return -1; - if (rClass.equals(PidType.NCID)) - return 1; - - if (lClass.equals(PidType.GBIF)) - return -1; - if (rClass.equals(PidType.GBIF)) - return 1; - if (lClass.equals(PidType.nct)) return -1; if (rClass.equals(PidType.nct)) return 1; - if (lClass.equals(PidType.urn)) + if (lClass.equals(PidType.pdb)) return -1; - if (rClass.equals(PidType.urn)) + if (rClass.equals(PidType.pdb)) return 1; return 0; From 893ac4a77bd4617f08fce83d6e27d074c07bc489 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 2 Dec 2020 09:30:06 +0100 Subject: [PATCH 047/445] GenerateEntitiesApplication can be configured to hash the id value or not --- .../java/eu/dnetlib/dhp/schema/oaf/utils/PidBlacklist.java | 2 ++ .../dnetlib/dhp/schema/oaf/utils/PidBlacklistProvider.java | 2 ++ .../eu/dnetlib/dhp/schema/oaf/utils/pid_blacklist.json | 0 .../dnetlib/dhp/schema/oaf/utils/BlackListProviderTest.java | 2 ++ .../dnetlib/dhp/oa/graph/generate_entities_parameters.json | 6 ++++++ .../eu/dnetlib/dhp/oa/graph/raw_all/oozie_app/workflow.xml | 2 ++ 6 files changed, 14 insertions(+) create mode 100644 dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidBlacklist.java create mode 100644 dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidBlacklistProvider.java create mode 100644 dhp-common/src/main/resources/eu/dnetlib/dhp/schema/oaf/utils/pid_blacklist.json create mode 100644 dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/BlackListProviderTest.java diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidBlacklist.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidBlacklist.java new file mode 100644 index 000000000..85abbe350 --- /dev/null +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidBlacklist.java @@ -0,0 +1,2 @@ +package eu.dnetlib.dhp.schema.oaf.utils;public class PidBlacklist { +} diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidBlacklistProvider.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidBlacklistProvider.java new file mode 100644 index 000000000..e044bedea --- /dev/null +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidBlacklistProvider.java @@ -0,0 +1,2 @@ +package eu.dnetlib.dhp.schema.oaf.utils;public class PidBlacklistProvider { +} diff --git a/dhp-common/src/main/resources/eu/dnetlib/dhp/schema/oaf/utils/pid_blacklist.json b/dhp-common/src/main/resources/eu/dnetlib/dhp/schema/oaf/utils/pid_blacklist.json new file mode 100644 index 000000000..e69de29bb diff --git a/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/BlackListProviderTest.java b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/BlackListProviderTest.java new file mode 100644 index 000000000..bd2eb4ec3 --- /dev/null +++ b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/BlackListProviderTest.java @@ -0,0 +1,2 @@ +package eu.dnetlib.dhp.schema.oaf.utils;public class BlackListProviderTest { +} diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/generate_entities_parameters.json b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/generate_entities_parameters.json index 8342dde95..4b3ebba38 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/generate_entities_parameters.json +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/generate_entities_parameters.json @@ -22,5 +22,11 @@ "paramLongName": "isLookupUrl", "paramDescription": "the url of the ISLookupService", "paramRequired": true + }, + { + "paramName": "shi", + "paramLongName": "shouldHashId", + "paramDescription": "should ids be hashed?", + "paramRequired": false } ] \ No newline at end of file diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_all/oozie_app/workflow.xml b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_all/oozie_app/workflow.xml index d8146d9a2..823b185f8 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_all/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_all/oozie_app/workflow.xml @@ -270,6 +270,7 @@ --sourcePaths${contentPath}/db_claims,${contentPath}/oaf_claims,${contentPath}/odf_claims,${contentPath}/oaf_records_invisible --targetPath${workingDir}/entities_claim --isLookupUrl${isLookupUrl} + --shouldHashId${shouldHashId} @@ -317,6 +318,7 @@ --sourcePaths${contentPath}/db_records,${contentPath}/oaf_records,${contentPath}/odf_records --targetPath${workingDir}/entities --isLookupUrl${isLookupUrl} + --shouldHashId${shouldHashId} From 943b961cf63ae55e60fea2d626af557c5456545f Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 2 Dec 2020 09:30:34 +0100 Subject: [PATCH 048/445] introduced PidBlacklist --- .../schema/oaf/utils/IdentifierFactory.java | 25 +++++++------ .../dhp/schema/oaf/utils/PidBlacklist.java | 8 +++- .../oaf/utils/PidBlacklistProvider.java | 37 ++++++++++++++++++- .../dhp/schema/oaf/utils/pid_blacklist.json | 5 +++ .../oaf/utils/BlackListProviderTest.java | 17 ++++++++- .../schema/oaf/utils/publication_doi1.json | 2 +- 6 files changed, 79 insertions(+), 15 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 5c3a4c37c..a6b2ce29b 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -1,13 +1,12 @@ package eu.dnetlib.dhp.schema.oaf.utils; +import java.io.IOException; import java.io.Serializable; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import eu.dnetlib.dhp.schema.oaf.CleaningFunctions; @@ -48,7 +47,8 @@ public class IdentifierFactory implements Serializable { Map> pids = entity .getPid() .stream() - .filter(s -> pidFilter(s)) + .map(CleaningFunctions::normalizePidValue) + .filter(IdentifierFactory::pidFilter) .collect( Collectors .groupingBy( @@ -83,17 +83,21 @@ public class IdentifierFactory implements Serializable { } protected static boolean pidFilter(StructuredProperty s) { + final String pidValue = s.getValue(); if (Objects.isNull(s.getQualifier()) || - StringUtils.isBlank(s.getValue()) || - StringUtils.isBlank(s.getValue().replaceAll("(?:\\n|\\r|\\t|\\s)", ""))) { + StringUtils.isBlank(pidValue) || + StringUtils.isBlank(pidValue.replaceAll("(?:\\n|\\r|\\t|\\s)", ""))) { return false; } - if (CleaningFunctions.PID_BLACKLIST.contains(StringUtils.trim(s.getValue().toLowerCase()))) { + if (CleaningFunctions.PID_BLACKLIST.contains(pidValue)) { + return false; + } + if (PidBlacklistProvider.getBlacklist(s.getQualifier().getClassid()).contains(pidValue)) { return false; } switch (PidType.tryValueOf(s.getQualifier().getClassid())) { case doi: - final String doi = StringUtils.trim(StringUtils.lowerCase(s.getValue())); + final String doi = StringUtils.trim(StringUtils.lowerCase(pidValue)); return doi.matches(DOI_REGEX); case original: return false; @@ -103,13 +107,12 @@ public class IdentifierFactory implements Serializable { } private static String idFromPid(T entity, StructuredProperty s, boolean md5) { - final String value = CleaningFunctions.normalizePidValue(s).getValue(); return new StringBuilder() .append(StringUtils.substringBefore(entity.getId(), ID_PREFIX_SEPARATOR)) .append(ID_PREFIX_SEPARATOR) .append(createPrefix(s.getQualifier().getClassid())) .append(ID_SEPARATOR) - .append(md5 ? DHPUtils.md5(value) : value) + .append(md5 ? DHPUtils.md5(s.getValue()) : s.getValue()) .toString(); } diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidBlacklist.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidBlacklist.java index 85abbe350..0b8e5e3f1 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidBlacklist.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidBlacklist.java @@ -1,2 +1,8 @@ -package eu.dnetlib.dhp.schema.oaf.utils;public class PidBlacklist { + +package eu.dnetlib.dhp.schema.oaf.utils; + +import java.util.HashMap; +import java.util.HashSet; + +public class PidBlacklist extends HashMap> { } diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidBlacklistProvider.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidBlacklistProvider.java index e044bedea..1c1c21f92 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidBlacklistProvider.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidBlacklistProvider.java @@ -1,2 +1,37 @@ -package eu.dnetlib.dhp.schema.oaf.utils;public class PidBlacklistProvider { + +package eu.dnetlib.dhp.schema.oaf.utils; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import org.apache.commons.io.IOUtils; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class PidBlacklistProvider { + + private static final PidBlacklist blacklist; + + static { + try { + String json = IOUtils.toString(IdentifierFactory.class.getResourceAsStream("pid_blacklist.json")); + blacklist = new ObjectMapper().readValue(json, PidBlacklist.class); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static PidBlacklist getBlacklist() { + return blacklist; + } + + public static Set getBlacklist(String pidType) { + return Optional + .ofNullable(getBlacklist().get(pidType)) + .orElse(new HashSet<>()); + } + } diff --git a/dhp-common/src/main/resources/eu/dnetlib/dhp/schema/oaf/utils/pid_blacklist.json b/dhp-common/src/main/resources/eu/dnetlib/dhp/schema/oaf/utils/pid_blacklist.json index e69de29bb..05e8cde72 100644 --- a/dhp-common/src/main/resources/eu/dnetlib/dhp/schema/oaf/utils/pid_blacklist.json +++ b/dhp-common/src/main/resources/eu/dnetlib/dhp/schema/oaf/utils/pid_blacklist.json @@ -0,0 +1,5 @@ +{ + "doi" : [ "10.12739/10.12739", "10.11646/zootaxa.4404.1.1", "10.5281/zenodo.3678492", "10.11646/zootaxa.4757.1.1", "10.17176/20170811-142447", "10.6035/asparkia", "10.11646/zootaxa.4754.1.6", "10.11646/zootaxa.4784.1.1", "10.6035/millars", "10.11646/zootaxa.4776.1.1", "10.1590/1982-0224-20170094", "10.11646/zootaxa.4773.1.1", "10.11646/zootaxa.4744.1.1", "10.3897/zookeys.38.383", "10.1371/journal.", "10.5281/zenodo.3727017", "10.5252/zoosystema2019v41a15", "10.6035/dossiersf", "10.11646/zootaxa.4754.1.20", "10.6035/recerca", "10.11646/zootaxa.4428.1.1", "10.7179/psri", "10.11646/zootaxa.4785.1.1", "10.2478/aemnp-2018-0014", "10.17979/spudc.9788497497565", "10.2139/ssrn.2721313", "10.17979/spudc.9788497497749", "10.5281/zenodo.3760976", "10.11646/zootaxa.4381.1.1", "10.6035/tiempos", "10.11646/zootaxa.4754.1.10", "10.5281/zenodo.3776452", "10.11646/zootaxa.4754.1.16", "10.5252/zoosystema2019v41a26", "10.11646/zootaxa.4759.2.1", "10.11646/zootaxa.4741.1.1", "10.5252/zoosystema2019v41a4", "10.1145/nnnnnnn.nnnnnnn", "10.17979/spudc.9788497497169", "10.11646/zootaxa.4780.3.1", "10.11646/zootaxa.4663.1.1", "10.5281/zenodo.3748525", "10.5281/zenodo.3746744", "10.3920/978-90-8686-761-5", "10.14198/eurau18alicante", "10.5252/geodiversitas2019v41a8", "10.4126/38m-0000003", "10.5281/zenodo.3648511", "10.6035/clr", "10.4126/38m-0000004", "10.5281/zenodo.3732535", "10.5281/zenodo.3355776", "10.4126/38m-0000002", "10.11646/zootaxa.4763.3.3", "10.11646/zootaxa.4413.3.1", "10.1163/9789004416208_005", "10.4126/38m-0000001", "10.3897/zookeys.30.308", "10.4126/38m-0000000", "10.5281/zenodo.3739808", "10.5281/zenodo.3674873", "10.3161/00034541anz2020.70.1.003", "10.5281/zenodo.3738648", "10.11646/zootaxa.4765.1.1", "10.11646/zootaxa.4754.1.8", "10.3897/zookeys.36.306", "10.4230/lipics", "10.5281/zenodo.3758345", "10.3161/00034541anz2020.70.1.001", "10.3929/ethz-a-005427569", "10.11646/zootaxa.4772.1.1", "10.5281/zenodo.3677235", "10.11646/zootaxa.4766.1.1", "10.17509/jurnal", "10.1145/1235", "10.11646/zootaxa.4754.1.15", "10.2478/aemnp-2018-0018", "10.11646/zootaxa.4538.1.1", "10.11646/zootaxa.4740.1.1", "10.3897/zookeys.32.282", "10.3897/zookeys.2.56", "10.3897/zookeys.39.425", "10.11646/zootaxa.4514.3.3", "10.1007/978-94-007-1966-8", "10.3897/zookeys.26.214", "10.11646/zootaxa.4106.1.1", "10.3897/zookeys.22.219", "10.11646/zootaxa.4748.2.1", "10.5252/zoosystema2019v41a19", "10.3897/zookeys.22.122", "10.1080/00222933.2019.1634225", "10.11646/zootaxa.4632.1.1", "10.1007/s00259-016-3484-4", "10.3897/zookeys.19.221", "10.3897/zookeys.2.7", "10.11646/zootaxa.4777.1.1", "10.14279/depositonce-3753", "10.1111/apha.12712", "10.11646/zootaxa.4759.3.4", "10.11646/zootaxa.4754.1.9", "10.11646/zootaxa.4747.2.8", "10.5281/zenodo.3757451", "10.5281/zenodo.3740269", "10.5252/zoosystema2020v42a4", "10.1140/epje/i2013-13103-3", "10.1177/0301006619863862", "10.5281/zenodo.3726987", "10.12795/hid", "10.24042/jipf", "10.12795/e-rips", "10.1186/s12913-016-1423-5", "10.4126/38m-0000005", "10.3847/2041-8213/aa91c9", "10.1145/1122445.1122456", "10.1103/physrevlett.114.191803", "10.3920/978-90-8686-782-0", "10.11646/zootaxa.4739.1.1", "10.11646/zootaxa.4770.1.1", "10.21009/10.21009/jpd.081", "10.1080/15548627.2015.1100356", "10.12795/ricl", "10.3897/zookeys.34.309", "10.1080/00222933.2019.1692088", "10.4126/frl01-0064002", "10.1371/journal", "10.1175/1520-0485(2002)032", "10.3897/zookeys.22.152", "10.11646/zootaxa.4731.2.1", "10.4126/frl01-0064005", "10.11646/zootaxa.4738.1.1", "10.11646/zootaxa.4780.1.6", "10.4126/frl01-0064004", "10.6018/analesps.31.1.158071", "10.1007/jhep08(2016)045", "10.5281/zenodo.3759519", "10.4126/frl01-0064010", "10.11646/zootaxa.4537.1.1", "10.5281/zenodo.3713533", "10.5281/zenodo.3742020", "10.4126/frl01-0064014", "10.4126/frl01-0064001", "10.1000/isbn", "10.5281/zenodo.3777290", "10.4126/frl01-0064008", "10.1159/000440895", "10.3897/zookeys.31.140", "10.4126/frl01-0064003", "10.1080/00222933.2018.1524032", "10.21686/2500-3925-2014-6", "10.1016/j.bbr.2011.03.031", "10.4126/frl01-0064006", "10.4126/frl01-0064007", "10.4126/frl01-0064020", "10.4126/frl01-0064016", "10.2478/aemnp-2018-0013", "10.4126/frl01-0064021", "10.5281/zenodo.3754300", "10.15330/gal.29-30.", "10.3897/zookeys.2.4", "10.5252/zoosystema2019v41a7", "10.22435/bpk.v17i2", "10.4126/frl01-0063997", "10.3897/zookeys.11.160", "10.11646/zootaxa.4754.1.14", "10.4126/frl01-0064013", "10.1080/20013078.2018.1535750", "10.1016/j.", "10.4126/frl01-0064011", "10.1002/ece3.2579", "10.1088/0264-9381/28/9/094001", "10.3897/zookeys.2.25", "10.4126/frl01-0064019", "10.4126/frl01-0063994", "10.4126/frl01-0064135", "10.4126/frl01-0063998", "10.12795/ppa", "10.4126/frl01-0064009", "10.11646/zootaxa.4769.1.1", "10.11646/zootaxa.4419.1.1", "10.11646/zootaxa.4733.1.1", "10.4126/frl01-0063993", "10.3161/15081109acc2016.18.1.005", "10.11646/zootaxa.4763.1.2", "10.11646/zootaxa.4754.1.19", "10.4126/frl01-0064136", "10.4126/frl01-0064159", "10.4126/frl01-0063999", "10.4126/frl01-0064161", "10.1089/ten.tea.2015.5000.abstracts", "10.1002/(issn)1521-3773", "10.1140/epjc/s10052-015-3325-9", "10.1016/j.physletb.2016.04.050", "10.1007/jhep04(2015)117", "10.1111/gcb.14904", "10.1016/s0140-6736(17)32129-3", "10.11646/zootaxa.4748.1.1", "10.4126/frl01-0064078", "10.1140/epjc/s10052-015-3408-7", "10.1002/(issn)1097-4652", "10.1007/jhep06(2015)121", "10.1007/jhep09(2014)103", "10.1016/j.gca.2007.06.021", "10.1007/jhep09(2015)049", "10.3897/zookeys.4.32", "10.6101/azq/0002", "10.11646/zootaxa.4764.1.1", "10.11646/zootaxa.4772.1.5", "10.4126/frl01-0064000", "10.4126/frl01-0064131", "10.1016/j.physletb.2015.08.061", "10.1007/jhep01(2015)069", "10.1016/j.physletb.2016.06.039", "10.1016/j.physletb.2015.07.011", "10.1007/jhep04(2015)116", "10.3920/978-90-8686-797-4", "10.1016/j.physletb.2015.12.020", "10.1016/j.physletb.2015.04.042", "10.1016/j.physletb.2016.06.004", "10.1140/epjc/s10052-015-3261-8", "10.1016/j.physletb.2015.10.067", "10.1016/j.physletb.2015.07.065", "10.1163/1876312x-00002195", "10.1016/j.physletb.2013.12.010", "10.1016/j.physletb.2013.01.024", "10.1007/jhep11(2014)056", "10.1007/jhep12(2017)142", "10.1002/pds.4864", "10.1140/epjc/s10052-015-3262-7", "10.1016/j.physletb.2014.09.054", "10.1140/epjc/s10052-015-3373-1", "10.1007/jhep03(2015)041", "10.1016/j.physletb.2016.02.047", "10.4126/frl01-0064018", "10.1016/j.physletb.2014.01.042", "10.1007/jhep09(2014)037", "10.1007/978-94-017-7285-3", "10.1007/s00424-013-1401-2", "10.1007/s00259-017-3822-1", "10.1177/0301006616671273", "10.1007/jhep09(2014)112", "10.1007/jhep06(2015)116", "10.1140/epjc/s10052-018-6243-9", "10.1140/epjc/s10052-017-4692-1", "10.1007/jhep10(2015)144", "10.1007/jhep07(2017)107", "10.1007/jhep11(2014)088", "10.1016/j.physletb.2014.01.006", "10.1007/jhep01(2018)055", "10.1016/j.physletb.2016.03.060", "10.1140/epjc/s10052-019-6904-3", "10.11646/zootaxa.4737.1.1", "10.3934/xx.xx.xx.xx", "10.11646/zootaxa.4758.2.1", "10.1016/j.physletb.2015.10.004", "10.1016/j.physletb.2015.07.053", "10.5798/diclemedj.0921.2012.04.0184", "10.1007/jhep04(2014)169", "10.4126/frl01-0064160", "10.3989/aem.2001.v31.i2", "10.1039/x0xx00000x", "10.11646/zootaxa.3856.4.1", "10.4126/frl01-0064133", "10.1007/jhep05(2015)078", "10.1016/j.physletb.2012.08.020", "10.1007/jhep07(2015)032", "10.1159/000090218", "10.1016/j.physletb.2014.03.015", "10.1007/jhep09(2015)108", "10.1007/jhep09(2015)050", "10.1007/jhep01(2014)163", "10.1016/j.physletb.2014.11.026", "10.1140/epjc/s10052-016-4580-0", "10.1140/epjc/s10052-014-3109-7", "10.1140/epjc/s10052-014-3231-6", "10.1007/jhep02(2014)088", "10.1016/j.physletb.2016.01.056", "10.1016/j.physletb.2015.08.047", "10.1016/j.physletb.2015.12.039", "10.1007/jhep11(2015)071", "10.1140/epjc/s10052-015-3853-3", "10.1007/jhep04(2015)124", "10.1016/j.physletb.2015.07.010", "10.5281/zenodo.3413524", "10.1007/jhep04(2014)031", "10.1007/jhep07(2015)157", "10.1103/physrevd.90.052008", "10.1007/jhep11(2014)118", "10.3920/978-90-8686-708-0", "10.5281/zenodo.1136235", "10.1103/physrevd.86.032003", "10.1016/j.physletb.2016.01.032", "10.1007/jhep03(2018)174", "10.1007/jhep10(2017)182", "10.1140/epjst/e2019-900045-4", "10.1016/j.physletb.2015.06.070", "10.1140/epjc/s10052-016-4067-z", "10.1016/j.physletb.2015.11.042", "10.1007/jhep04(2018)033", "10.1007/jhep09(2014)145", "10.1016/j.physletb.2016.08.055", "10.1016/j.physletb.2015.04.002", "10.1007/jhep03(2014)032", "10.1140/epjc/s10052-017-5491-4", "10.1016/j.physletb.2015.09.062", "10.1016/j.physletb.2014.12.003", "10.1016/j.physletb.2015.03.017", "10.1140/epjc/s10052-014-3195-6", "10.1140/epjc/s10052-016-4034-8", "10.1140/epjc/s10052-016-4070-4", "10.1140/epjc/s10052-018-5693-4", "10.4126/frl01-0064017", "10.1007/jhep08(2014)173", "10.1016/j.physletb.2014.06.076", "10.1016/j.physletb.2018.11.064", "10.1140/epjc/s10052-017-4988-1", "10.11646/zootaxa.4258.4.3", "10.11646/zootaxa.4766.1.2", "10.11646/zootaxa.4780.1.1", "10.5281/zenodo.3693943", "10.4126/frl01-0064129", "10.15330/gal.28.", "10.1007/jhep02(2016)145", "10.1007/jhep04(2014)172", "10.1007/jhep04(2016)005", "10.1007/jhep03(2016)125", "10.1016/j.physletb.2018.02.033", "10.1007/jhep08(2017)052", "10.1007/jhep12(2017)085", "10.1007/jhep09(2014)176", "10.1007/jhep12(2017)024", "10.1140/epjc/s10052-018-5686-3", "10.1016/j.physletb.2016.11.035", "10.1016/j.physletb.2015.12.017", "10.1140/epjc/s10052-015-3542-2", "10.1140/epjc/s10052-014-3071-4", "10.1103/physrevd.97.032009", "10.1140/epjc/s10052-015-3306-z", "10.1016/j.physletb.2017.12.043", "10.1140/epjc/s10052-014-3233-4", "10.1016/j.physletb.2018.09.013", "10.1016/j.gca.2007.06.014", "10.1016/j.physletb.2016.05.005", "10.1038/s41586-019-1171-x", "10.1016/j.physletb.2016.05.087", "10.1007/jhep06(2018)022", "10.1016/j.physletb.2016.01.057", "10.1016/j.physletb.2018.03.023", "10.1140/epjc/s10052-015-3351-7", "10.1126/science.aap8757", "10.1007/jhep09(2015)137", "10.1007/jhep01(2015)063", "10.1007/jhep01(2018)126", "10.1016/j.gca.2007.06.020", "10.1140/epjc/s10052-018-5595-5", "10.1016/j.physletb.2015.02.015", "10.1016/j.physletb.2014.06.077", "10.1007/jhep12(2017)059", "10.1007/jhep10(2017)141", "10.1007/jhep02(2014)107", "10.1140/epjc/s10052-014-2965-5", "10.1016/j.physletb.2015.07.079", "10.1007/jhep10(2017)112", "10.1140/epjc/s10052-014-2982-4", "10.1007/jhep05(2016)160", "10.1016/j.physletb.2016.07.030", "10.1140/epjc/s10052-014-3168-9", "10.1140/epjc/s10052-018-5583-9", "10.1140/epjc/s10052-016-4184-8", "10.1007/jhep08(2015)105", "10.1007/jhep05(2015)061", "10.1103/physrevd.97.032003", "10.1140/epjc/s10052-014-3190-y", "10.1016/j.physletb.2012.10.061", "10.1140/epjc/s10052-014-2941-0", "10.1016/j.physletb.2016.02.002", "10.1016/j.physletb.2016.05.033", "10.1007/jhep01(2014)096", "10.1007/jhep09(2015)201", "10.1016/j.physletb.2016.01.010", "10.1016/j.physletb.2015.07.037", "10.1007/jhep07(2015)042", "10.1016/j.physletb.2016.05.044", "10.1016/j.physletb.2016.05.088", "10.3897/zookeys.2.2", "10.1007/jhep11(2015)018", "10.1007/jhep11(2015)189", "10.1016/j.physletb.2016.10.014", "10.1007/jhep06(2015)080", "10.1016/j.physletb.2014.11.042", "10.1140/epjc/s10052-014-3157-z", "10.1140/epjc/s10052-015-3406-9", "10.1016/j.physletb.2016.02.056", "10.1016/j.physletb.2015.03.054", "10.1140/epjc/s10052-016-4574-y", "10.5252/geodiversitas2019v41a15", "10.1007/jhep09(2014)094", "10.1140/epjc/s10052-017-5486-1", "10.1007/jhep03(2018)095", "10.11646/zootaxa.4736.1.1", "10.11646/zootaxa.4766.2.1", "10.5281/zenodo.3762392", "10.5281/zenodo.3761958", "10.11646/zootaxa.4403.3.2", "10.1553/iswimab", "10.11646/zootaxa.3750.5.1", "10.4126/frl01-0064134", "10.1103/physrevd.87.032002", "10.1140/epjc/s10052-013-2676-3", "10.1007/jhep02(2015)153", "10.1007/jhep08(2017)006", "10.1016/j.physletb.2016.11.005", "10.1007/jhep01(2013)029", "10.1007/jhep10(2017)132", "10.1016/j.physletb.2013.01.034", "10.1016/j.physletb.2016.03.046", "10.1140/epjc/s10052-016-3988-x", "10.1016/j.physletb.2016.07.006", "10.1140/epjc/s10052-018-5752-x", "10.1140/epjc/s10052-015-3454-1", "10.1002/ece3.1303", "10.1007/jhep02(2014)013", "10.1007/jhep06(2016)081", "10.1140/epjc/s10052-014-3117-7", "10.1007/jhep09(2017)084", "10.1016/j.physletb.2017.09.078", "10.1007/jhep08(2016)005", "10.1007/jhep01(2015)020", "10.1140/epjc/s10052-017-4852-3", "10.1016/j.physletb.2018.02.045", "10.7818/sibecolandaeetmeeting.2019", "10.1007/jhep11(2014)104", "10.1007/jhep05(2018)077", "10.1016/j.physletb.2016.11.045", "10.1016/j.physletb.2016.10.042", "10.1140/epjc/s10052-016-4203-9", "10.1007/jhep01(2015)068", "10.1007/jhep06(2016)093", "10.1016/j.physletb.2015.09.051", "10.1140/epjc/s10052-015-3534-2", "10.1007/jhep09(2014)087", "10.1016/j.physletb.2014.05.055", "10.1016/j.physletb.2014.02.033", "10.1140/epjc/s10052-017-5225-7", "10.1140/epjc/s10052-017-5442-0", "10.1016/s0140-6736(18)32335-3", "10.1016/j.physletb.2017.11.049", "10.1007/jhep06(2018)166", "10.1016/j.physletb.2016.05.002", "10.1140/epjc/s10052-016-4219-1", "10.1140/epjst/e2019-900087-0", "10.1007/jhep01(2016)166", "10.1007/jhep01(2018)097", "10.1016/j.physletb.2017.11.043", "10.1016/j.physletb.2018.04.036", "10.1140/epjc/s10052-018-5607-5", "10.1007/jhep12(2017)034", "10.1007/jhep11(2016)112", "10.1007/jhep06(2014)008", "10.1140/epjc/s10052-012-2261-1", "10.1016/j.physletb.2014.08.039", "10.1016/s0140-6736(16)31919-5", "10.1140/epjc/s10052-019-7058-z", "10.1016/j.physletb.2014.07.053", "10.1007/jhep01(2015)053", "10.1016/j.physletb.2016.07.042", "10.1007/jhep08(2014)103", "10.1007/jhep06(2015)100", "10.1140/epjc/s10052-015-3363-3", "10.1140/epjc/s10052-017-4915-5", "10.1140/epjc/s10052-014-3023-z", "10.1140/epjc/s10052-017-5315-6", "10.1140/epjc/s10052-016-4050-8", "10.3389/fpsyt.2017.00244", "10.1016/j.physletb.2014.10.002", "10.1007/jhep07(2015)162", "10.1007/jhep08(2014)174", "10.3897/zookeys.2.23", "10.1007/jhep07(2017)014", "10.1007/jhep04(2016)035", "10.1140/epjc/s10052-017-4984-5", "10.1007/jhep02(2016)156", "10.1016/j.physletb.2016.03.039", "10.1007/jhep07(2018)115", "10.3897/zookeys.34.268", "10.1007/jhep02(2016)122", "10.1016/j.physletb.2012.03.022", "10.1016/j.physletb.2018.09.019", "10.1016/j.physletb.2018.09.024", "10.1051/0004-6361/201629272", "10.1103/physrevc.97.024904", "10.1140/epjc/s10052-016-4521-y", "10.1140/epjc/s10052-016-4176-8", "10.1140/epjc/s10052-014-3134-6", "10.1140/epjc/s10052-016-4110-0", "10.1007/jhep07(2017)121", "10.1007/jhep07(2018)153", "10.1007/jhep03(2018)115", "10.1007/jhep04(2018)060", "10.11606/1807-0205/2020.60.06", "10.4126/frl01-0064015", "10.1007/jhep09(2017)020", "10.1016/j.physletb.2014.04.023", "10.1016/j.physletb.2015.02.048", "10.1007/jhep02(2018)032", "10.1016/j.physletb.2018.01.001", "10.1140/epjc/s10052-015-3852-4", "10.1007/jhep10(2014)087", "10.11646/zootaxa.4630.1.1", "10.5281/zenodo.3742118", "10.4126/frl01-0064022", "10.11646/zootaxa.4758.3.1", "10.11646/zootaxa.4772.3.1", "10.11646/zootaxa.4576.3.5", "10.4126/frl01-0064125", "10.1007/jhep12(2017)017", "10.4126/frl01-0064162", "10.4126/frl01-0064138", "10.1007/jhep06(2014)124", "10.1007/jhep06(2016)059", "10.1007/jhep06(2014)035", "10.1103/physrevd.90.052005", "10.1007/jhep11(2017)062", "10.3847/2041-8213/aa9aed", "10.1016/j.physletb.2016.06.080", "10.1007/jhep10(2017)073", "10.1007/jhep03(2018)167", "10.1016/j.physletb.2018.11.065", "10.1140/epjc/s10052-017-5081-5", "10.1140/epjc/s10052-015-3500-z", "10.1140/epjc/s10052-017-5445-x", "10.1016/j.physletb.2014.01.049", "10.1007/jhep03(2018)172", "10.1016/j.physletb.2015.03.048", "10.1016/j.physletb.2018.11.032", "10.1007/jhep05(2018)025", "10.1016/j.physletb.2016.08.052", "10.1016/j.physletb.2014.09.008", "10.1103/physrevlett.120.071802", "10.1016/j.physletb.2018.01.049", "10.1016/j.physletb.2016.06.017", "10.1016/j.physletb.2016.04.005", "10.1007/jhep06(2018)031", "10.1007/jhep01(2016)079", "10.1007/jhep10(2017)006", "10.1140/epjc/s10052-018-5740-1", "10.1016/j.physletb.2015.01.034", "10.1007/jhep10(2017)005", "10.1016/j.physletb.2018.04.007", "10.1007/jhep04(2015)164", "10.1140/epjc/s10052-018-5691-6", "10.1007/jhep05(2018)148", "10.1007/jhep03(2018)003", "10.1140/epjc/s10052-014-3076-z", "10.1016/j.physletb.2016.02.015", "10.1103/physrevd.97.072003", "10.1016/j.physletb.2017.11.054", "10.1140/epjc/s10052-011-1849-1", "10.1007/jhep09(2016)175", "10.1016/j.physletb.2017.12.011", "10.1007/jhep04(2014)103", "10.1007/jhep12(2014)017", "10.1016/j.physletb.2014.09.048", "10.1140/epjc/s10052-019-7202-9", "10.1007/jhep04(2014)191", "10.1007/jhep07(2013)163", "10.1140/epjc/s10052-014-3130-x", "10.1007/jhep04(2016)023", "10.1016/j.physletb.2015.07.023", "10.1140/epjc/s10052-018-6500-y", "10.1016/j.physletb.2015.04.045", "10.1007/jhep09(2017)053", "10.1007/jhep10(2017)180", "10.1140/epjc/s10052-017-4912-8", "10.1007/jhep10(2016)129", "10.3920/978-90-8686-816-2", "10.1007/jhep01(2017)099", "10.1007/jhep01(2018)045", "10.1007/jhep04(2015)025", "10.1016/j.physletb.2018.02.050", "10.1103/physrevlett.116.032301", "10.1007/jhep08(2017)029", "10.1007/jhep08(2017)073", "10.1016/j.physletb.2014.11.059", "10.1007/jhep01(2013)131", "10.1007/jhep06(2014)112", "10.1016/j.physletb.2017.09.066", "10.1140/epjc/s10052-014-2883-6", "10.1094/mpmi", "10.1007/jhep11(2017)195", "10.1007/jhep06(2018)108", "10.1007/jhep09(2018)139", "10.1016/j.physletb.2016.12.005", "10.1140/epjc/s10052-017-5349-9", "10.1016/j.physletb.2012.08.021", "10.1016/j.physletb.2014.10.032", "10.1007/jhep09(2017)088", "10.1140/epjc/s10052-015-3425-6", "10.1007/jhep01(2018)054", "10.1103/physrevlett.110.182302", "10.1140/epjc/s10052-017-5317-4", "10.1007/jhep01(2017)117", "10.1016/j.physletb.2017.12.006", "10.1016/j.physletb.2018.02.004", "10.1016/j.physletb.2018.02.025", "10.1016/j.physletb.2016.02.055", "10.1016/j.physletb.2016.04.061", "10.1140/epjc/s10052-015-3372-2", "10.1016/j.physletb.2015.02.051", "10.1016/j.physletb.2014.11.049", "10.1007/jhep09(2016)001", "10.1016/j.physletb.2016.03.017", "10.1007/jhep06(2016)067", "10.1140/epjc/s10052-015-3543-1", "10.1140/epjc/s10052-017-4911-9", "10.1007/jhep07(2013)122", "10.1140/epjc/s10052-019-6855-8", "10.1140/epjc/s10052-019-6540-y", "10.1007/jhep06(2014)009", "10.1007/jhep05(2019)043", "10.1016/j.physletb.2016.01.028", "10.1103/physrevlett.120.231801", "10.1140/epjc/s10052-016-4325-0", "10.1007/jhep07(2018)127", "10.1016/j.physletb.2016.05.003", "10.1140/epjc/s10052-017-4644-9", "10.1140/epjc/s10052-017-4700-5", "10.1007/jhep06(2018)107", "10.1016/j.physletb.2018.01.042", "10.1140/epjc/s10052-018-5624-4", "10.1007/jhep08(2016)139", "10.1007/jhep05(2018)195", "10.1103/physrevd.97.052012", "10.1140/epjc/s10052-016-3978-z", "10.1007/jhep05(2019)088", "10.1140/epjc/s10052-017-5079-z", "10.1140/epjc/s10052-016-4205-7", "10.1007/jhep01(2016)006", "10.1140/epjc/s10052-016-4286-3", "10.1016/j.physletb.2017.04.071", "10.1103/physrevd.97.012007", "10.1016/j.physletb.2018.01.077", "10.1007/jhep04(2018)073", "10.1016/j.physletb.2015.09.057", "10.1007/jhep07(2018)032", "10.1140/epjc/s10052-015-3435-4", "10.1007/jhep11(2017)010", "10.1093/isd/ixaa002", "10.1016/j.physletb.2018.03.035", "10.1007/jhep10(2018)031", "10.1016/s0140-6736(18)31891-9", "10.1140/epjc/s10052-018-6148-7", "10.1016/j.physletb.2018.03.057", "10.1140/epjc/s10052-019-6632-8", "10.1016/j.physletb.2015.11.071", "10.1140/epjc/s10052-018-5605-7", "10.1016/j.physletb.2018.10.073", "10.1140/epjc/s10052-019-7387-y", "10.1007/jhep06(2019)143", "10.1140/epjc/s10052-018-5567-9", "10.1140/epjc/s10052-019-6909-y", "10.1002/(sici)1521-3978(199901)47:1/3", "10.5281/zenodo.3758372", "10.4126/frl01-0064041", "10.1140/epjc/s10052-014-3129-3", "10.11646/zootaxa.4685.1.1", "10.11646/zootaxa.4756.1.1", "10.6101/azq/0001", "10.14582/duzg", "10.1016/j.physletb.2012.11.039", "10.4126/frl01-0064191", "10.1016/j.physletb.2013.12.029", "10.1007/jhep10(2013)189", "10.1051/0004-6361/201629512", "10.1007/jhep01(2013)116", "10.2312/gfz.lis.2016.001", "10.1016/j.physletb.2013.01.040", "10.1103/physrevd.90.112005", "10.1140/epjc/s10052-015-3726-9", "10.1007/s11682-013-9269-5", "10.1007/jhep02(2017)071", "10.1016/j.physletb.2016.09.040", "10.1007/jhep02(2017)117", "10.1007/jhep08(2016)009", "10.1103/physrevd.97.052010", "10.1007/jhep09(2017)032", "10.1103/physrevd.97.032005", "10.1140/epjc/s10052-017-4965-8", "10.1016/j.physletb.2016.08.042", "10.1016/j.physletb.2017.10.039", "10.1007/jhep03(2016)127", "10.1140/epjc/s10052-014-3034-9", "10.1007/jhep03(2017)113", "10.1007/jhep11(2018)040", "10.1140/epjc/s10052-018-6457-x", "10.1140/epjc/s10052-016-4041-9", "10.1140/epjc/s10052-018-6219-9", "10.1140/epjc/s10052-016-4149-y", "10.1007/jhep10(2017)072", "10.1140/epjc/s10052-016-4083-z", "10.1140/epjc/s10052-016-3956-5", "10.1007/jhep04(2016)073", "10.1007/jhep06(2016)177", "10.1016/j.physletb.2018.03.084", "10.1007/jhep10(2015)128", "10.1007/jhep03(2018)166", "10.1140/epjc/s10052-015-3491-9", "10.1016/j.physletb.2015.04.060", "10.1103/physrevd.92.112001", "10.1140/epjc/s10052-015-3367-z", "10.1007/jhep10(2017)019", "10.1007/jhep10(2017)131", "10.1016/j.physletb.2018.08.057", "10.1007/jhep01(2016)096", "10.1016/j.physletb.2017.09.053", "10.1007/jhep07(2017)013", "10.1007/jhep01(2019)030", "10.1007/jhep11(2016)110", "10.1016/j.physletb.2012.02.044", "10.1140/epjc/s10052-017-5192-z", "10.1007/jhep03(2015)022", "10.1140/epjc/s10052-019-6847-8", "10.1093/database/baz085", "10.1140/epjc/s10052-015-3451-4", "10.1007/jhep11(2017)029", "10.1140/epjc/s10052-015-3517-3", "10.1007/jhep07(2017)001", "10.1007/jhep09(2016)074", "10.1103/physrevd.97.072016", "10.1007/jhep05(2018)006", "10.1103/physrevlett.120.081801", "10.1103/physrevlett.120.161802", "10.1103/physrevlett.120.202005", "10.5281/zenodo.1299123", "10.5281/zenodo.3777294" ], + "pmid" : [], + "pmc" : [] +} \ No newline at end of file diff --git a/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/BlackListProviderTest.java b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/BlackListProviderTest.java index bd2eb4ec3..7cab5ed9c 100644 --- a/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/BlackListProviderTest.java +++ b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/BlackListProviderTest.java @@ -1,2 +1,17 @@ -package eu.dnetlib.dhp.schema.oaf.utils;public class BlackListProviderTest { + +package eu.dnetlib.dhp.schema.oaf.utils; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class BlackListProviderTest { + + @Test + public void blackListTest() { + + Assertions.assertNotNull(PidBlacklistProvider.getBlacklist()); + Assertions.assertNotNull(PidBlacklistProvider.getBlacklist().get("doi")); + Assertions.assertTrue(PidBlacklistProvider.getBlacklist().get("doi").size() > 0); + Assertions.assertNull(PidBlacklistProvider.getBlacklist("xxx")); + } } diff --git a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi1.json b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi1.json index 6865d0ff6..da90a64d0 100644 --- a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi1.json +++ b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi1.json @@ -1 +1 @@ -{"id":"50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f","pid":[{"qualifier":{"classid":"doi"},"value":"10.1016/j.cmet.2011.03.013"},{"qualifier":{"classid":"urn"},"value":"urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2"},{"qualifier":{"classid":"scp-number"},"value":"79953761260"},{"qualifier":{"classid":"pmc"},"value":"21459329"}]} \ No newline at end of file +{"id":"50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f","pid":[ {"qualifier":{"classid":"doi"},"value":"10.12739/10.12739"},{"qualifier":{"classid":"doi"},"value":"10.1016/j.cmet.2011.03.013"},{"qualifier":{"classid":"urn"},"value":"urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2"},{"qualifier":{"classid":"scp-number"},"value":"79953761260"},{"qualifier":{"classid":"pmc"},"value":"21459329"}]} \ No newline at end of file From 21ddcf3a7393a414501478ae90a2e367b56e806a Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 7 Dec 2020 13:45:18 +0100 Subject: [PATCH 049/445] actions promotion can optionally avoid grouping objects by id (configured via shouldGroupById parameter) --- .../PromoteActionPayloadForGraphTableJob.java | 38 ++++++++++++------- ...load_for_graph_table_input_parameters.json | 6 +++ .../wf/dataset/oozie_app/workflow.xml | 6 +++ .../wf/main/oozie_app/workflow.xml | 5 +++ .../oozie_app/workflow.xml | 6 +++ .../wf/publication/oozie_app/workflow.xml | 6 +++ .../wf/software/oozie_app/workflow.xml | 6 +++ ...moteActionPayloadForGraphTableJobTest.java | 8 +++- 8 files changed, 66 insertions(+), 15 deletions(-) diff --git a/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadForGraphTableJob.java b/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadForGraphTableJob.java index 5fa9e6723..be775f358 100644 --- a/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadForGraphTableJob.java +++ b/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadForGraphTableJob.java @@ -68,6 +68,12 @@ public class PromoteActionPayloadForGraphTableJob { MergeAndGet.Strategy strategy = MergeAndGet.Strategy.valueOf(parser.get("mergeAndGetStrategy").toUpperCase()); logger.info("strategy: {}", strategy); + Boolean shouldGroupById = Optional + .ofNullable(parser.get("shouldGroupById")) + .map(Boolean::valueOf) + .orElse(true); + logger.info("shouldGroupById: {}", shouldGroupById); + Class rowClazz = (Class) Class.forName(graphTableClassName); Class actionPayloadClazz = (Class) Class.forName(actionPayloadClassName); @@ -89,7 +95,8 @@ public class PromoteActionPayloadForGraphTableJob { outputGraphTablePath, strategy, rowClazz, - actionPayloadClazz); + actionPayloadClazz, + shouldGroupById); }); } @@ -109,18 +116,18 @@ public class PromoteActionPayloadForGraphTableJob { } private static void promoteActionPayloadForGraphTable( - SparkSession spark, - String inputGraphTablePath, - String inputActionPayloadPath, - String outputGraphTablePath, - MergeAndGet.Strategy strategy, - Class rowClazz, - Class actionPayloadClazz) { + SparkSession spark, + String inputGraphTablePath, + String inputActionPayloadPath, + String outputGraphTablePath, + MergeAndGet.Strategy strategy, + Class rowClazz, + Class actionPayloadClazz, Boolean shouldGroupById) { Dataset rowDS = readGraphTable(spark, inputGraphTablePath, rowClazz); Dataset actionPayloadDS = readActionPayload(spark, inputActionPayloadPath, actionPayloadClazz); Dataset result = promoteActionPayloadForGraphTable( - rowDS, actionPayloadDS, strategy, rowClazz, actionPayloadClazz) + rowDS, actionPayloadDS, strategy, rowClazz, actionPayloadClazz, shouldGroupById) .map((MapFunction) value -> value, Encoders.bean(rowClazz)); saveGraphTable(result, outputGraphTablePath); @@ -174,7 +181,8 @@ public class PromoteActionPayloadForGraphTableJob { Dataset actionPayloadDS, MergeAndGet.Strategy strategy, Class rowClazz, - Class actionPayloadClazz) { + Class actionPayloadClazz, + Boolean shouldGroupById) { logger .info( "Promoting action payload for graph table: payload={}, table={}", @@ -198,9 +206,13 @@ public class PromoteActionPayloadForGraphTableJob { rowClazz, actionPayloadClazz); - return PromoteActionPayloadFunctions - .groupGraphTableByIdAndMerge( - joinedAndMerged, rowIdFn, mergeRowsAndGetFn, zeroFn, isNotZeroFn, rowClazz); + if (shouldGroupById) { + return PromoteActionPayloadFunctions + .groupGraphTableByIdAndMerge( + joinedAndMerged, rowIdFn, mergeRowsAndGetFn, zeroFn, isNotZeroFn, rowClazz); + } else { + return joinedAndMerged; + } } private static SerializableSupplier zeroFn(Class clazz) { diff --git a/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/promote/promote_action_payload_for_graph_table_input_parameters.json b/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/promote/promote_action_payload_for_graph_table_input_parameters.json index e111f156e..f5eae8a30 100644 --- a/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/promote/promote_action_payload_for_graph_table_input_parameters.json +++ b/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/promote/promote_action_payload_for_graph_table_input_parameters.json @@ -40,5 +40,11 @@ "paramLongName": "mergeAndGetStrategy", "paramDescription": "strategy for merging graph table objects with action payload instances, MERGE_FROM_AND_GET or SELECT_NEWER_AND_GET", "paramRequired": true + }, + { + "paramName": "sgid", + "paramLongName": "shouldGroupById", + "paramDescription": "indicates whether the promotion operation should group objects in the graph by id or not", + "paramRequired": true } ] \ No newline at end of file diff --git a/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/dataset/oozie_app/workflow.xml b/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/dataset/oozie_app/workflow.xml index f95349935..4dc250c29 100644 --- a/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/dataset/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/dataset/oozie_app/workflow.xml @@ -24,6 +24,10 @@ mergeAndGetStrategy strategy for merging graph table objects with action payload instances, MERGE_FROM_AND_GET or SELECT_NEWER_AND_GET + + shouldGroupById + indicates whether the promotion operation should group objects in the graph by id or not + sparkDriverMemory memory for driver process @@ -111,6 +115,7 @@ --actionPayloadClassNameeu.dnetlib.dhp.schema.oaf.Dataset --outputGraphTablePath${workingDir}/dataset --mergeAndGetStrategy${mergeAndGetStrategy} + --shouldGroupById${shouldGroupById} @@ -162,6 +167,7 @@ --actionPayloadClassNameeu.dnetlib.dhp.schema.oaf.Result --outputGraphTablePath${outputGraphRootPath}/dataset --mergeAndGetStrategy${mergeAndGetStrategy} + --shouldGroupById${shouldGroupById} diff --git a/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/main/oozie_app/workflow.xml b/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/main/oozie_app/workflow.xml index 25afc34c9..393f04e89 100644 --- a/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/main/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/main/oozie_app/workflow.xml @@ -56,6 +56,11 @@ mergeAndGetStrategy strategy for merging graph table objects with action payload instances, MERGE_FROM_AND_GET or SELECT_NEWER_AND_GET + + shouldGroupById + false + indicates whether the promotion operation should group objects in the graph by id or not + sparkDriverMemory memory for driver process diff --git a/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/otherresearchproduct/oozie_app/workflow.xml b/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/otherresearchproduct/oozie_app/workflow.xml index 0deb1b945..7bac760e2 100644 --- a/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/otherresearchproduct/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/otherresearchproduct/oozie_app/workflow.xml @@ -24,6 +24,10 @@ mergeAndGetStrategy strategy for merging graph table objects with action payload instances, MERGE_FROM_AND_GET or SELECT_NEWER_AND_GET + + shouldGroupById + indicates whether the promotion operation should group objects in the graph by id or not + sparkDriverMemory memory for driver process @@ -110,6 +114,7 @@ --actionPayloadClassNameeu.dnetlib.dhp.schema.oaf.OtherResearchProduct --outputGraphTablePath${workingDir}/otherresearchproduct --mergeAndGetStrategy${mergeAndGetStrategy} + --shouldGroupById${shouldGroupById} @@ -161,6 +166,7 @@ --actionPayloadClassNameeu.dnetlib.dhp.schema.oaf.Result --outputGraphTablePath${outputGraphRootPath}/otherresearchproduct --mergeAndGetStrategy${mergeAndGetStrategy} + --shouldGroupById${shouldGroupById} diff --git a/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/publication/oozie_app/workflow.xml b/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/publication/oozie_app/workflow.xml index 70400a123..2450bdad7 100644 --- a/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/publication/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/publication/oozie_app/workflow.xml @@ -24,6 +24,10 @@ mergeAndGetStrategy strategy for merging graph table objects with action payload instances, MERGE_FROM_AND_GET or SELECT_NEWER_AND_GET + + shouldGroupById + indicates whether the promotion operation should group objects in the graph by id or not + sparkDriverMemory memory for driver process @@ -111,6 +115,7 @@ --actionPayloadClassNameeu.dnetlib.dhp.schema.oaf.Publication --outputGraphTablePath${workingDir}/publication --mergeAndGetStrategy${mergeAndGetStrategy} + --shouldGroupById${shouldGroupById} @@ -162,6 +167,7 @@ --actionPayloadClassNameeu.dnetlib.dhp.schema.oaf.Result --outputGraphTablePath${outputGraphRootPath}/publication --mergeAndGetStrategy${mergeAndGetStrategy} + --shouldGroupById${shouldGroupById} diff --git a/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/software/oozie_app/workflow.xml b/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/software/oozie_app/workflow.xml index 396e27721..b5673b18f 100644 --- a/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/software/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/wf/software/oozie_app/workflow.xml @@ -24,6 +24,10 @@ mergeAndGetStrategy strategy for merging graph table objects with action payload instances, MERGE_FROM_AND_GET or SELECT_NEWER_AND_GET + + shouldGroupById + indicates whether the promotion operation should group objects in the graph by id or not + sparkDriverMemory memory for driver process @@ -110,6 +114,7 @@ --actionPayloadClassNameeu.dnetlib.dhp.schema.oaf.Software --outputGraphTablePath${workingDir}/software --mergeAndGetStrategy${mergeAndGetStrategy} + --shouldGroupById${shouldGroupById} @@ -161,6 +166,7 @@ --actionPayloadClassNameeu.dnetlib.dhp.schema.oaf.Result --outputGraphTablePath${outputGraphRootPath}/software --mergeAndGetStrategy${mergeAndGetStrategy} + --shouldGroupById${shouldGroupById} diff --git a/dhp-workflows/dhp-actionmanager/src/test/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadForGraphTableJobTest.java b/dhp-workflows/dhp-actionmanager/src/test/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadForGraphTableJobTest.java index 129daadcc..79ab55e07 100644 --- a/dhp-workflows/dhp-actionmanager/src/test/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadForGraphTableJobTest.java +++ b/dhp-workflows/dhp-actionmanager/src/test/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadForGraphTableJobTest.java @@ -101,7 +101,9 @@ public class PromoteActionPayloadForGraphTableJobTest { "-outputGraphTablePath", "", "-mergeAndGetStrategy", - MergeAndGet.Strategy.SELECT_NEWER_AND_GET.name() + MergeAndGet.Strategy.SELECT_NEWER_AND_GET.name(), + "--shouldGroupById", + "true" })); // then @@ -141,7 +143,9 @@ public class PromoteActionPayloadForGraphTableJobTest { "-outputGraphTablePath", outputGraphTableDir.toString(), "-mergeAndGetStrategy", - strategy.name() + strategy.name(), + "--shouldGroupById", + "true" }); // then From 491ad2475052dec98c329906352413d0d0e6001d Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 9 Dec 2020 09:10:33 +0100 Subject: [PATCH 050/445] introduced filtering for DOIs in graph cleaning workflow --- .../dhp/schema/oaf/CleaningFunctions.java | 28 +++++++++++++++++-- .../schema/oaf/utils/IdentifierFactory.java | 18 ++++++------ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index 5f191e9a9..8ce4285d6 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -13,7 +13,7 @@ import eu.dnetlib.dhp.schema.common.ModelConstants; public class CleaningFunctions { - public static final String DOI_URL_PREFIX_REGEX = "(^http(s?):\\/\\/)(((dx\\.)?doi\\.org)|(handle\\.test\\.datacite\\.org))\\/"; + public static final String DOI_PREFIX_REGEX = "^.*10\\."; public static final String ORCID_PREFIX_REGEX = "^http(s?):\\/\\/orcid\\.org\\/"; public static final String CLEANING_REGEX = "(?:\\n|\\r|\\t)"; @@ -146,6 +146,7 @@ public class CleaningFunctions { .filter(sp -> Objects.nonNull(sp.getQualifier())) .filter(sp -> StringUtils.isNotBlank(sp.getQualifier().getClassid())) .map(CleaningFunctions::normalizePidValue) + .filter(CleaningFunctions::filterPid) .collect(Collectors.toList())); } if (Objects.isNull(r.getResourcetype()) || StringUtils.isBlank(r.getResourcetype().getClassid())) { @@ -253,6 +254,29 @@ public class CleaningFunctions { classid, classname, scheme, scheme); } + /** + * Utility method that filter PID values on a per-type basis. + * @param pid the PID whose value will be checked. + * @return true the PID containing the normalised value. + */ + private static boolean filterPid(StructuredProperty pid) { + String value = Optional + .ofNullable(pid.getValue()) + .map(s -> StringUtils.replaceAll(s, "\\s", "")) + .orElse(""); + if (StringUtils.isBlank(value)) { + return false; + } + switch (pid.getQualifier().getClassid()) { + + // TODO add cleaning for more PID types as needed + case "doi": + return value.startsWith("10."); + default: + return true; + } + } + /** * Utility method that normalises PID values on a per-type basis. * @param pid the PID whose value will be normalised. @@ -267,7 +291,7 @@ public class CleaningFunctions { // TODO add cleaning for more PID types as needed case "doi": - pid.setValue(value.toLowerCase().replaceAll(DOI_URL_PREFIX_REGEX, "")); + pid.setValue(value.toLowerCase().replaceAll(DOI_PREFIX_REGEX, "10.")); break; } return pid; diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index a6b2ce29b..9978194ac 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -1,18 +1,18 @@ package eu.dnetlib.dhp.schema.oaf.utils; -import java.io.IOException; -import java.io.Serializable; -import java.util.*; -import java.util.stream.Collectors; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; - import eu.dnetlib.dhp.schema.oaf.CleaningFunctions; import eu.dnetlib.dhp.schema.oaf.OafEntity; import eu.dnetlib.dhp.schema.oaf.StructuredProperty; import eu.dnetlib.dhp.utils.DHPUtils; +import org.apache.commons.lang3.StringUtils; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; /** * Factory class for OpenAIRE identifiers in the Graph @@ -21,8 +21,6 @@ public class IdentifierFactory implements Serializable { public static final String ID_SEPARATOR = "::"; public static final String ID_PREFIX_SEPARATOR = "|"; - public final static String ID_REGEX = "^[0-9][0-9]\\" + ID_PREFIX_SEPARATOR + ".{12}" + ID_SEPARATOR - + "[a-zA-Z0-9]{32}$"; public final static String DOI_REGEX = "(^10\\.[0-9]{4,9}\\/[-._;()\\/:a-zA-Z0-9]+$)|" + "(^10\\.1002\\/[^\\s]+$)|" + From 1a87a1effd1133b03386805462e0066c763af2d5 Mon Sep 17 00:00:00 2001 From: antleb Date: Mon, 30 Nov 2020 00:48:10 +0200 Subject: [PATCH 051/445] added last step to update cache --- .../dhp/oa/graph/stats/oozie_app/config-default.xml | 4 ++++ .../dhp/oa/graph/stats/oozie_app/updateCache.sh | 4 ++++ .../dhp/oa/graph/stats/oozie_app/workflow.xml | 12 ++++++++++++ 3 files changed, 20 insertions(+) create mode 100644 dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/updateCache.sh diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/config-default.xml b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/config-default.xml index 2cd53a37b..9331d4ac5 100644 --- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/config-default.xml +++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/config-default.xml @@ -27,4 +27,8 @@ oozie.wf.workflow.notification.url {serviceUrl}/v1/oozieNotification/jobUpdate?jobId=$jobId%26status=$status + + stats_tool_api_url + ${stats_tool_api_url} + \ No newline at end of file diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/updateCache.sh b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/updateCache.sh new file mode 100644 index 000000000..36e74a556 --- /dev/null +++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/updateCache.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +curl --request GET $1/cache/updateCache + diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml index d6cc14e25..0b6a00df1 100644 --- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml @@ -287,6 +287,18 @@ ${wf:appPath()}/scripts/step19.sql impala-shell.sh + + + + + + + ${jobTracker} + ${nameNode} + updateCache.sh + ${stats_tool_api_url} + updateCache.sh + From ded23922756e284f62563286c0a0065b47f5a8a2 Mon Sep 17 00:00:00 2001 From: antleb Date: Wed, 2 Dec 2020 15:41:56 +0200 Subject: [PATCH 052/445] initial implementation of the promote wf --- dhp-workflows/dhp-stats-promote/pom.xml | 32 ++ .../graph/stats/oozie_app/config-default.xml | 34 ++ .../oa/graph/stats/oozie_app/impala-shell.sh | 18 ++ .../oa/graph/stats/oozie_app/promoteCache.sh | 4 + .../graph/stats/oozie_app/scripts/step1.sql | 8 + .../graph/stats/oozie_app/scripts/step10.sql | 21 ++ .../graph/stats/oozie_app/scripts/step11.sql | 44 +++ .../graph/stats/oozie_app/scripts/step12.sql | 38 +++ .../graph/stats/oozie_app/scripts/step13.sql | 59 ++++ .../graph/stats/oozie_app/scripts/step14.sql | 49 +++ .../graph/stats/oozie_app/scripts/step15.sql | 36 +++ .../graph/stats/oozie_app/scripts/step16.sql | 80 +++++ .../stats/oozie_app/scripts/step16_5.sql | 55 ++++ .../stats/oozie_app/scripts/step16_6.sql | 32 ++ .../graph/stats/oozie_app/scripts/step17.sql | 207 ++++++++++++ .../graph/stats/oozie_app/scripts/step18.sql | 8 + .../graph/stats/oozie_app/scripts/step19.sql | 8 + .../graph/stats/oozie_app/scripts/step2.sql | 44 +++ .../graph/stats/oozie_app/scripts/step3.sql | 36 +++ .../graph/stats/oozie_app/scripts/step4.sql | 36 +++ .../graph/stats/oozie_app/scripts/step5.sql | 36 +++ .../graph/stats/oozie_app/scripts/step6.sql | 30 ++ .../graph/stats/oozie_app/scripts/step7.sql | 31 ++ .../graph/stats/oozie_app/scripts/step8.sql | 58 ++++ .../graph/stats/oozie_app/scripts/step9.sql | 12 + .../dhp/oa/graph/stats/oozie_app/workflow.xml | 302 ++++++++++++++++++ 26 files changed, 1318 insertions(+) create mode 100644 dhp-workflows/dhp-stats-promote/pom.xml create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/config-default.xml create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/impala-shell.sh create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/promoteCache.sh create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step1.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step10.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step11.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step12.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step13.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step14.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step15.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16_5.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16_6.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step17.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step18.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step19.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step2.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step3.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step4.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step5.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step6.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step7.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step8.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step9.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml diff --git a/dhp-workflows/dhp-stats-promote/pom.xml b/dhp-workflows/dhp-stats-promote/pom.xml new file mode 100644 index 000000000..c64c2f58e --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/pom.xml @@ -0,0 +1,32 @@ + + + + dhp-workflows + eu.dnetlib.dhp + 1.2.4-SNAPSHOT + + 4.0.0 + dhp-stats-promote + + + org.apache.spark + spark-core_2.11 + + + org.apache.spark + spark-sql_2.11 + + + + + + pl.project13.maven + git-commit-id-plugin + 2.1.11 + + false + + + + + diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/config-default.xml b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/config-default.xml new file mode 100644 index 000000000..9331d4ac5 --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/config-default.xml @@ -0,0 +1,34 @@ + + + jobTracker + ${jobTracker} + + + nameNode + ${nameNode} + + + oozie.use.system.libpath + true + + + oozie.action.sharelib.for.spark + spark2 + + + hive_metastore_uris + thrift://iis-cdh5-test-m3.ocean.icm.edu.pl:9083 + + + hive_jdbc_url + jdbc:hive2://iis-cdh5-test-m3.ocean.icm.edu.pl:10000 + + + oozie.wf.workflow.notification.url + {serviceUrl}/v1/oozieNotification/jobUpdate?jobId=$jobId%26status=$status + + + stats_tool_api_url + ${stats_tool_api_url} + + \ No newline at end of file diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/impala-shell.sh b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/impala-shell.sh new file mode 100644 index 000000000..70112dc7b --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/impala-shell.sh @@ -0,0 +1,18 @@ +export PYTHON_EGG_CACHE=/home/$(whoami)/.python-eggs +export link_folder=/tmp/impala-shell-python-egg-cache-$(whoami) +if ! [ -L $link_folder ] +then + rm -Rf "$link_folder" + ln -sfn ${PYTHON_EGG_CACHE}${link_folder} ${link_folder} +fi + +echo "Getting file from " $3 +hdfs dfs -copyToLocal $3 + +echo "Running impala shell make the new database visible" +impala-shell -q "INVALIDATE METADATA;" + +echo "Running impala shell to compute new table stats" +impala-shell -d $1 -f $2 +echo "Impala shell finished" +rm $2 diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/promoteCache.sh b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/promoteCache.sh new file mode 100644 index 000000000..2d28377fb --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/promoteCache.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +curl --request GET $1/cache/promoteCache + diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step1.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step1.sql new file mode 100644 index 000000000..9697a1dc8 --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step1.sql @@ -0,0 +1,8 @@ +-------------------------------------------------------------- +-------------------------------------------------------------- +-- Stats database creation +-------------------------------------------------------------- +-------------------------------------------------------------- + +DROP database IF EXISTS ${stats_db_name} CASCADE; +CREATE database ${stats_db_name}; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step10.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step10.sql new file mode 100644 index 000000000..46ff295f4 --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step10.sql @@ -0,0 +1,21 @@ +------------------------------------------------------------------------------------------------ +------------------------------------------------------------------------------------------------ +-- Tables/views from external tables/views (Fundref, Country, CountyGDP, roarmap, rndexpediture) +------------------------------------------------------------------------------------------------ +------------------------------------------------------------------------------------------------ +CREATE OR REPLACE VIEW ${stats_db_name}.fundref AS SELECT * FROM ${external_stats_db_name}.fundref; +CREATE OR REPLACE VIEW ${stats_db_name}.country AS SELECT * FROM ${external_stats_db_name}.country; +CREATE OR REPLACE VIEW ${stats_db_name}.countrygdp AS SELECT * FROM ${external_stats_db_name}.countrygdp; +CREATE OR REPLACE VIEW ${stats_db_name}.roarmap AS SELECT * FROM ${external_stats_db_name}.roarmap; +CREATE OR REPLACE VIEW ${stats_db_name}.rndexpediture AS SELECT * FROM ${external_stats_db_name}.rndexpediture; +CREATE OR REPLACE VIEW ${stats_db_name}.context AS SELECT * FROM ${external_stats_db_name}.context; +CREATE OR REPLACE VIEW ${stats_db_name}.category AS SELECT * FROM ${external_stats_db_name}.category; +CREATE OR REPLACE VIEW ${stats_db_name}.concept AS SELECT * FROM ${external_stats_db_name}.concept; + + +------------------------------------------------------------------------------------------------ +------------------------------------------------------------------------------------------------ +-- Creation date of the database +------------------------------------------------------------------------------------------------ +------------------------------------------------------------------------------------------------ +create table ${stats_db_name}.creation_date as select date_format(current_date(), 'dd-MM-yyyy') as date; \ No newline at end of file diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step11.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step11.sql new file mode 100644 index 000000000..13e141459 --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step11.sql @@ -0,0 +1,44 @@ +---------------------------------------------------------------- +---------------------------------------------------------------- +-- Post processing - Updates on main tables +---------------------------------------------------------------- +---------------------------------------------------------------- + +--Datasource temporary table updates +UPDATE ${stats_db_name}.datasource_tmp SET harvested='true' WHERE datasource_tmp.id IN (SELECT DISTINCT d.id FROM ${stats_db_name}.datasource_tmp d, ${stats_db_name}.result_datasources rd WHERE d.id=rd.datasource); + +-- Project temporary table update and final project table creation with final updates that can not be applied to ORC tables +UPDATE ${stats_db_name}.project_tmp SET haspubs='yes' WHERE project_tmp.id IN (SELECT pr.id FROM ${stats_db_name}.project_results pr, ${stats_db_name}.result r WHERE pr.result=r.id AND r.type='publication'); + +DROP TABLE IF EXISTS ${stats_db_name}.project; +CREATE TABLE ${stats_db_name}.project stored as parquet as +SELECT p.id , p.acronym, p.title, p.funder, p.funding_lvl0, p.funding_lvl1, p.funding_lvl2, p.ec39, p.type, p.startdate, p.enddate, p.start_year, p.end_year, p.duration, +CASE WHEN prr1.id IS NULL THEN 'no' ELSE 'yes' END AS haspubs, +CASE WHEN prr1.id IS NULL THEN 0 ELSE prr1.np END AS numpubs, +CASE WHEN prr2.id IS NULL THEN 0 ELSE prr2.daysForlastPub END AS daysforlastpub, +CASE WHEN prr2.id IS NULL THEN 0 ELSE prr2.dp END AS delayedpubs, +p.callidentifier, p.code +FROM ${stats_db_name}.project_tmp p +LEFT JOIN (SELECT pr.id, count(distinct pr.result) AS np + FROM ${stats_db_name}.project_results pr INNER JOIN ${stats_db_name}.result r ON pr.result=r.id + WHERE r.type='publication' + GROUP BY pr.id) AS prr1 on prr1.id = p.id +LEFT JOIN (SELECT pp.id, max(datediff(to_date(r.date), to_date(pp.enddate)) ) AS daysForlastPub , count(distinct r.id) AS dp + FROM ${stats_db_name}.project_tmp pp, ${stats_db_name}.project_results pr, ${stats_db_name}.result r + WHERE pp.id=pr.id AND pr.result=r.id AND r.type='publication' AND datediff(to_date(r.date), to_date(pp.enddate)) > 0 + GROUP BY pp.id) AS prr2 + ON prr2.id = p.id; + +-- Publication temporary table updates +UPDATE ${stats_db_name}.publication_tmp SET delayed = 'yes' WHERE publication_tmp.id IN (SELECT distinct r.id FROM stats_wf_db_obs.result r, ${stats_db_name}.project_results pr, ${stats_db_name}.project_tmp p WHERE r.id=pr.result AND pr.id=p.id AND to_date(r.date)-to_date(p.enddate) > 0); + +-- Dataset temporary table updates +UPDATE ${stats_db_name}.dataset_tmp SET delayed = 'yes' WHERE dataset_tmp.id IN (SELECT distinct r.id FROM stats_wf_db_obs.result r, ${stats_db_name}.project_results pr, ${stats_db_name}.project_tmp p WHERE r.id=pr.result AND pr.id=p.id AND to_date(r.date)-to_date(p.enddate) > 0); + +-- Software temporary table updates +UPDATE ${stats_db_name}.software_tmp SET delayed = 'yes' WHERE software_tmp.id IN (SELECT distinct r.id FROM ${stats_db_name}.result r, ${stats_db_name}.project_results pr, ${stats_db_name}.project_tmp p WHERE r.id=pr.result AND pr.id=p.id AND to_date(r.date)-to_date(p.enddate) > 0); + +-- Oherresearchproduct temporary table updates +UPDATE ${stats_db_name}.otherresearchproduct_tmp SET delayed = 'yes' WHERE otherresearchproduct_tmp.id IN (SELECT distinct r.id FROM ${stats_db_name}.result r, ${stats_db_name}.project_results pr, ${stats_db_name}.project_tmp p WHERE r.id=pr.result AND pr.id=p.id AND to_date(r.date)-to_date(p.enddate) > 0); + +CREATE OR REPLACE VIEW ${stats_db_name}.project_results_publication AS SELECT result_projects.id AS result, result_projects.project AS project_results, result.date as resultdate, project.enddate as projectenddate, result_projects.daysfromend AS daysfromend FROM ${stats_db_name}.result_projects, ${stats_db_name}.result, ${stats_db_name}.project WHERE result_projects.id=result.id AND result.type='publication' AND project.id=result_projects.project; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step12.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step12.sql new file mode 100644 index 000000000..25439852e --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step12.sql @@ -0,0 +1,38 @@ +------------------------------------------------------------------------------------------------------ +-- Creating parquet tables from the updated temporary tables and removing unnecessary temporary tables +------------------------------------------------------------------------------------------------------ + +DROP TABLE IF EXISTS ${stats_db_name}.datasource; +CREATE TABLE ${stats_db_name}.datasource stored AS parquet AS SELECT * FROM ${stats_db_name}.datasource_tmp; + +DROP TABLE IF EXISTS ${stats_db_name}.publication; +CREATE TABLE ${stats_db_name}.publication stored AS parquet AS SELECT * FROM ${stats_db_name}.publication_tmp; + +DROP TABLE IF EXISTS ${stats_db_name}.dataset; +CREATE TABLE ${stats_db_name}.dataset stored AS parquet AS SELECT * FROM ${stats_db_name}.dataset_tmp; + +DROP TABLE IF EXISTS ${stats_db_name}.software; +CREATE TABLE ${stats_db_name}.software stored AS parquet AS SELECT * FROM ${stats_db_name}.software_tmp; + +DROP TABLE IF EXISTS ${stats_db_name}.otherresearchproduct; +CREATE TABLE ${stats_db_name}.otherresearchproduct stored AS parquet AS SELECT * FROM ${stats_db_name}.otherresearchproduct_tmp; + +DROP TABLE ${stats_db_name}.project_tmp; +DROP TABLE ${stats_db_name}.datasource_tmp; +DROP TABLE ${stats_db_name}.publication_tmp; +DROP TABLE ${stats_db_name}.dataset_tmp; +DROP TABLE ${stats_db_name}.software_tmp; +DROP TABLE ${stats_db_name}.otherresearchproduct_tmp; + +---------------------------------------------- +-- Re-creating views from final parquet tables +--------------------------------------------- + +-- Result +CREATE OR REPLACE VIEW ${stats_db_name}.result AS SELECT *, bestlicence AS access_mode FROM ${stats_db_name}.publication UNION ALL SELECT *, bestlicence as access_mode FROM ${stats_db_name}.software UNION ALL SELECT *, bestlicence AS access_mode FROM ${stats_db_name}.dataset UNION ALL SELECT *, bestlicence AS access_mode FROM ${stats_db_name}.otherresearchproduct; + + +------------------------------------------------------------------------------- +-- To see with Antonis if the following is needed and where it should be placed +------------------------------------------------------------------------------- +CREATE TABLE ${stats_db_name}.numbers_country AS SELECT org.country AS country, count(distinct rd.datasource) AS datasources, count(distinct r.id) AS publications FROM ${stats_db_name}.result r, ${stats_db_name}.result_datasources rd, ${stats_db_name}.datasource d, ${stats_db_name}.datasource_organizations dor, ${stats_db_name}.organization org WHERE r.id=rd.id AND rd.datasource=d.id AND d.id=dor.id AND dor.organization=org.id AND r.type='publication' AND r.bestlicence='Open Access' GROUP BY org.country; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step13.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step13.sql new file mode 100644 index 000000000..795770313 --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step13.sql @@ -0,0 +1,59 @@ +------------------------------------------------------ +------------------------------------------------------ +-- Additional relations +-- +-- Sources related tables/views +------------------------------------------------------ +------------------------------------------------------ +CREATE TABLE IF NOT EXISTS ${stats_db_name}.publication_sources as +SELECT p.id, case when d.id is null then 'other' else p.datasource end as datasource +FROM ( + SELECT substr(p.id, 4) as id, substr(datasource, 4) as datasource +from ${openaire_db_name}.publication p lateral view explode(p.collectedfrom.key) c as datasource) p +LEFT OUTER JOIN +( + SELECT substr(d.id, 4) id + from ${openaire_db_name}.datasource d + WHERE d.datainfo.deletedbyinference=false) d on p.datasource = d.id; + +CREATE TABLE IF NOT EXISTS ${stats_db_name}.dataset_sources as +SELECT p.id, case when d.id is null then 'other' else p.datasource end as datasource +FROM ( + SELECT substr(p.id, 4) as id, substr(datasource, 4) as datasource +from ${openaire_db_name}.dataset p lateral view explode(p.collectedfrom.key) c as datasource) p +LEFT OUTER JOIN +( + SELECT substr(d.id, 4) id + from ${openaire_db_name}.datasource d + WHERE d.datainfo.deletedbyinference=false) d on p.datasource = d.id; + +CREATE TABLE IF NOT EXISTS ${stats_db_name}.software_sources as +SELECT p.id, case when d.id is null then 'other' else p.datasource end as datasource +FROM ( + SELECT substr(p.id, 4) as id, substr(datasource, 4) as datasource +from ${openaire_db_name}.software p lateral view explode(p.collectedfrom.key) c as datasource) p +LEFT OUTER JOIN +( + SELECT substr(d.id, 4) id + from ${openaire_db_name}.datasource d + WHERE d.datainfo.deletedbyinference=false) d on p.datasource = d.id; + +CREATE TABLE IF NOT EXISTS ${stats_db_name}.otherresearchproduct_sources as +SELECT p.id, case when d.id is null then 'other' else p.datasource end as datasource +FROM ( + SELECT substr(p.id, 4) as id, substr(datasource, 4) as datasource +from ${openaire_db_name}.otherresearchproduct p lateral view explode(p.collectedfrom.key) c as datasource) p +LEFT OUTER JOIN +( + SELECT substr(d.id, 4) id + from ${openaire_db_name}.datasource d + WHERE d.datainfo.deletedbyinference=false) d on p.datasource = d.id; + +CREATE VIEW IF NOT EXISTS ${stats_db_name}.result_sources AS +SELECT * FROM ${stats_db_name}.publication_sources +UNION ALL +SELECT * FROM ${stats_db_name}.dataset_sources +UNION ALL +SELECT * FROM ${stats_db_name}.software_sources +UNION ALL +SELECT * FROM ${stats_db_name}.otherresearchproduct_sources; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step14.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step14.sql new file mode 100644 index 000000000..4a56b5d68 --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step14.sql @@ -0,0 +1,49 @@ +------------------------------------------------------ +------------------------------------------------------ +-- Additional relations +-- +-- Licences related tables/views +------------------------------------------------------ +------------------------------------------------------ +CREATE TABLE IF NOT EXISTS ${stats_db_name}.publication_licenses AS +SELECT substr(p.id, 4) as id, licenses.value as type +from ${openaire_db_name}.publication p LATERAL VIEW explode(p.instance.license) instances as licenses +where licenses.value is not null and licenses.value != '' and p.datainfo.deletedbyinference=false; + +CREATE TABLE IF NOT EXISTS ${stats_db_name}.dataset_licenses AS +SELECT substr(p.id, 4) as id, licenses.value as type +from ${openaire_db_name}.dataset p LATERAL VIEW explode(p.instance.license) instances as licenses +where licenses.value is not null and licenses.value != '' and p.datainfo.deletedbyinference=false; + +CREATE TABLE IF NOT EXISTS ${stats_db_name}.software_licenses AS +SELECT substr(p.id, 4) as id, licenses.value as type +from ${openaire_db_name}.software p LATERAL VIEW explode(p.instance.license) instances as licenses +where licenses.value is not null and licenses.value != '' and p.datainfo.deletedbyinference=false; + +CREATE TABLE IF NOT EXISTS ${stats_db_name}.otherresearchproduct_licenses AS +SELECT substr(p.id, 4) as id, licenses.value as type +from ${openaire_db_name}.otherresearchproduct p LATERAL VIEW explode(p.instance.license) instances as licenses +where licenses.value is not null and licenses.value != '' and p.datainfo.deletedbyinference=false; + +CREATE VIEW IF NOT EXISTS ${stats_db_name}.result_licenses AS +SELECT * FROM ${stats_db_name}.publication_licenses +UNION ALL +SELECT * FROM ${stats_db_name}.dataset_licenses +UNION ALL +SELECT * FROM ${stats_db_name}.software_licenses +UNION ALL +SELECT * FROM ${stats_db_name}.otherresearchproduct_licenses; + +CREATE TABLE IF NOT EXISTS ${stats_db_name}.organization_pids AS +select substr(o.id, 4) as id, ppid.qualifier.classname as type, ppid.value as pid +from ${openaire_db_name}.organization o lateral view explode(o.pid) pids as ppid; + +CREATE TABLE IF NOT EXISTS ${stats_db_name}.organization_sources as +SELECT o.id, case when d.id is null then 'other' else o.datasource end as datasource +FROM ( + SELECT substr(o.id, 4) as id, substr(instances.instance.key, 4) as datasource + from ${openaire_db_name}.organization o lateral view explode(o.collectedfrom) instances as instance) o + LEFT OUTER JOIN ( + SELECT substr(d.id, 4) id + from ${openaire_db_name}.datasource d + WHERE d.datainfo.deletedbyinference=false) d on o.datasource = d.id; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step15.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step15.sql new file mode 100644 index 000000000..60b37048b --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step15.sql @@ -0,0 +1,36 @@ +------------------------------------------------------ +------------------------------------------------------ +-- Additional relations +-- +-- Refereed related tables/views +------------------------------------------------------ +------------------------------------------------------ + +CREATE TABLE IF NOT EXISTS ${stats_db_name}.publication_refereed as +select substr(r.id, 4) as id, inst.refereed.classname as refereed +from ${openaire_db_name}.publication r lateral view explode(r.instance) instances as inst +where r.datainfo.deletedbyinference=false; + +CREATE TABLE IF NOT EXISTS ${stats_db_name}.dataset_refereed as +select substr(r.id, 4) as id, inst.refereed.classname as refereed +from ${openaire_db_name}.dataset r lateral view explode(r.instance) instances as inst +where r.datainfo.deletedbyinference=false; + +CREATE TABLE IF NOT EXISTS ${stats_db_name}.software_refereed as +select substr(r.id, 4) as id, inst.refereed.classname as refereed +from ${openaire_db_name}.software r lateral view explode(r.instance) instances as inst +where r.datainfo.deletedbyinference=false; + +CREATE TABLE IF NOT EXISTS ${stats_db_name}.otherresearchproduct_refereed as +select substr(r.id, 4) as id, inst.refereed.classname as refereed +from ${openaire_db_name}.otherresearchproduct r lateral view explode(r.instance) instances as inst +where r.datainfo.deletedbyinference=false; + +CREATE VIEW IF NOT EXISTS ${stats_db_name}.result_refereed as +select * from ${stats_db_name}.publication_refereed +union all +select * from ${stats_db_name}.dataset_refereed +union all +select * from ${stats_db_name}.software_refereed +union all +select * from ${stats_db_name}.otherresearchproduct_refereed; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16.sql new file mode 100644 index 000000000..33849b960 --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16.sql @@ -0,0 +1,80 @@ +---------------------------------------------------- +-- Shortcuts for various definitions in stats db --- +---------------------------------------------------- + +-- Peer reviewed: +-- Results that have been collected from Crossref +create table ${stats_db_name}.result_peerreviewed as +with peer_reviewed as ( + select distinct r.id as id + from ${stats_db_name}.result r + join ${stats_db_name}.result_sources rs on rs.id=r.id + join ${stats_db_name}.datasource d on d.id=rs.datasource + where d.name='Crossref') +select distinct peer_reviewed.id as id, true as peer_reviewed +from peer_reviewed +union all +select distinct r.id as id, false as peer_reviewed +from ${stats_db_name}.result r +left outer join peer_reviewed pr on pr.id=r.id +where pr.id is null; + +-- Green OA: +-- OA results that are hosted by an Institutional repository and have NOT been harvested from a DOAJ journal. +create table ${stats_db_name}.result_greenoa as +with result_green as ( + select distinct r.id as id + from ${stats_db_name}.result r + join ${stats_db_name}.result_datasources rd on rd.id=r.id + join ${stats_db_name}.datasource d on d.id=rd.datasource + left outer join ( + select rd.id from ${stats_db_name}.result_datasources rd + join ${stats_db_name}.datasource d on rd.datasource=d.id + join ${stats_db_name}.datasource_sources sds on sds.id=d.id + join ${stats_db_name}.datasource sd on sd.id=sds.datasource + where sd.name='DOAJ-ARTICLES' + ) as doaj on doaj.id=r.id + where r.bestlicence in ('Open Access', 'Open Source') and d.type='Institutional Repository' and doaj.id is null) +select distinct result_green.id, true as green +from result_green +union all +select distinct r.id as id, false as green +from ${stats_db_name}.result r +left outer join result_green rg on rg.id=r.id +where rg.id is null; + +-- GOLD OA: +-- OA results that have been harvested from a DOAJ journal. +create table ${stats_db_name}.result_gold as +with result_gold as ( + select distinct r.id as id + from ${stats_db_name}.result r + join ${stats_db_name}.result_datasources rd on rd.id=r.id + join ${stats_db_name}.datasource d on d.id=rd.datasource + join ${stats_db_name}.datasource_sources sds on sds.id=d.id + join ${stats_db_name}.datasource sd on sd.id=sds.datasource + where r.type='publication' and r.bestlicence='Open Access' and sd.name='DOAJ-Articles') +select distinct result_gold.id, true as gold +from result_gold +union all +select distinct r.id, false as gold +from ${stats_db_name}.result r +where r.id not in (select id from result_gold); + +-- shortcut result-country through the organization affiliation +create table ${stats_db_name}.result_affiliated_country as +select r.id as id, o.country as country +from ${stats_db_name}.result r +join ${stats_db_name}.result_organization ro on ro.id=r.id +join ${stats_db_name}.organization o on o.id=ro.organization +where o.country is not null and o.country!=''; + +-- shortcut result-country through datasource of deposition +create table ${stats_db_name}.result_deposited_country as +select r.id as id, o.country as country +from ${stats_db_name}.result r +join ${stats_db_name}.result_datasources rd on rd.id=r.id +join ${stats_db_name}.datasource d on d.id=rd.datasource +join ${stats_db_name}.datasource_organizations dor on dor.id=d.id +join ${stats_db_name}.organization o on o.id=dor.organization +where o.country is not null and o.country!=''; \ No newline at end of file diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16_5.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16_5.sql new file mode 100644 index 000000000..f737c1ea6 --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16_5.sql @@ -0,0 +1,55 @@ +-- replace the creation of the result view to include the boolean fields from the previous tables (green, gold, +-- peer reviewed) +drop table if exists ${stats_db_name}.result_tmp; +CREATE TABLE ${stats_db_name}.result_tmp ( + id STRING, + title STRING, + publisher STRING, + journal STRING, + `date` STRING, + `year` INT, + bestlicence STRING, + access_mode STRING, + embargo_end_date STRING, + delayed BOOLEAN, + authors INT, + source STRING, + abstract BOOLEAN, + type STRING , + peer_reviewed BOOLEAN, + green BOOLEAN, + gold BOOLEAN) +clustered by (id) into 100 buckets stored as orc tblproperties('transactional'='true'); + +insert into ${stats_db_name}.result_tmp +select r.id, r.title, r.publisher, r.journal, r.`date`, date_format(r.`date`, 'yyyy'), r.bestlicence, r.bestlicence, r.embargo_end_date, r.delayed, r.authors, r.source, r.abstract, r.type, pr.peer_reviewed, green.green, gold.gold +FROM ${stats_db_name}.publication r +LEFT OUTER JOIN ${stats_db_name}.result_peerreviewed pr on pr.id=r.id +LEFT OUTER JOIN ${stats_db_name}.result_greenoa green on green.id=r.id +LEFT OUTER JOIN ${stats_db_name}.result_gold gold on gold.id=r.id; + +insert into ${stats_db_name}.result_tmp +select r.id, r.title, r.publisher, r.journal, r.`date`, date_format(r.`date`, 'yyyy'), r.bestlicence, r.bestlicence, r.embargo_end_date, r.delayed, r.authors, r.source, r.abstract, r.type, pr.peer_reviewed, green.green, gold.gold +FROM ${stats_db_name}.dataset r +LEFT OUTER JOIN ${stats_db_name}.result_peerreviewed pr on pr.id=r.id +LEFT OUTER JOIN ${stats_db_name}.result_greenoa green on green.id=r.id +LEFT OUTER JOIN ${stats_db_name}.result_gold gold on gold.id=r.id; + +insert into ${stats_db_name}.result_tmp +select r.id, r.title, r.publisher, r.journal, r.`date`, date_format(r.`date`, 'yyyy'), r.bestlicence, r.bestlicence, r.embargo_end_date, r.delayed, r.authors, r.source, r.abstract, r.type, pr.peer_reviewed, green.green, gold.gold +FROM ${stats_db_name}.software r +LEFT OUTER JOIN ${stats_db_name}.result_peerreviewed pr on pr.id=r.id +LEFT OUTER JOIN ${stats_db_name}.result_greenoa green on green.id=r.id +LEFT OUTER JOIN ${stats_db_name}.result_gold gold on gold.id=r.id; + +insert into ${stats_db_name}.result_tmp +select r.id, r.title, r.publisher, r.journal, r.`date`, date_format(r.`date`, 'yyyy'), r.bestlicence, r.bestlicence, r.embargo_end_date, r.delayed, r.authors, r.source, r.abstract, r.type, pr.peer_reviewed, green.green, gold.gold +FROM ${stats_db_name}.otherresearchproduct r +LEFT OUTER JOIN ${stats_db_name}.result_peerreviewed pr on pr.id=r.id +LEFT OUTER JOIN ${stats_db_name}.result_greenoa green on green.id=r.id +LEFT OUTER JOIN ${stats_db_name}.result_gold gold on gold.id=r.id; + +drop table if exists ${stats_db_name}.result; +drop view if exists ${stats_db_name}.result; +create table ${stats_db_name}.result stored as parquet as select * from ${stats_db_name}.result_tmp; +drop table ${stats_db_name}.result_tmp; \ No newline at end of file diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16_6.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16_6.sql new file mode 100644 index 000000000..ced7bbc11 --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16_6.sql @@ -0,0 +1,32 @@ +------------------------------------------- +--- Extra tables, mostly used by indicators + +create table ${stats_db_name}.result_projectcount as +select r.id, count(distinct p.id) as count +from ${stats_db_name}.result r +left outer join ${stats_db_name}.result_projects rp on rp.id=r.id +left outer join ${stats_db_name}.project p on p.id=rp.project +group by r.id; + +create table ${stats_db_name}.result_fundercount as +select r.id, count(distinct p.funder) as count +from ${stats_db_name}.result r +left outer join ${stats_db_name}.result_projects rp on rp.id=r.id +left outer join ${stats_db_name}.project p on p.id=rp.project +group by r.id; + +create table ${stats_db_name}.project_resultcount as +with rcount as ( + select p.id as pid, count(distinct r.id) as `count`, r.type as type + from ${stats_db_name}.project p + left outer join ${stats_db_name}.result_projects rp on rp.project=p.id + left outer join ${stats_db_name}.result r on r.id=rp.id + group by r.type, p.id ) +select rcount.pid, sum(case when rcount.type='publication' then rcount.count else 0 end) as publications, + sum(case when rcount.type='dataset' then rcount.count else 0 end) as datasets, + sum(case when rcount.type='software' then rcount.count else 0 end) as software, + sum(case when rcount.type='other' then rcount.count else 0 end) as other +from rcount +group by rcount.pid; + +create view ${stats_db_name}.rndexpenditure as select * from stats_ext.rndexpediture \ No newline at end of file diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step17.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step17.sql new file mode 100644 index 000000000..5c102d014 --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step17.sql @@ -0,0 +1,207 @@ +------------------------------------------------------ +------------------------------------------------------ +-- Shadow schema table exchange +------------------------------------------------------ +------------------------------------------------------ + +-- Dropping old views +DROP VIEW IF EXISTS ${stats_db_shadow_name}.category; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.concept; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.context; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.country; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.countrygdp; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.creation_date; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_citations; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_classifications; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_concepts; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_datasources; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_languages; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_licenses; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_oids; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_pids; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_refereed; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_sources; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_topics; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.datasource; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.datasource_languages; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.datasource_oids; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.datasource_organizations; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.datasource_results; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.datasource_sources; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.funder; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.fundref; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.numbers_country; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.organization; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.organization_datasources; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.organization_pids; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.organization_projects; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.organization_sources; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_citations; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_classifications; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_concepts; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_datasources; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_languages; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_licenses; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_oids; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_pids; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_refereed; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_sources; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_topics; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.project; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.project_oids; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.project_organizations; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.project_results; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.project_resultcount; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.project_results_publication; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_citations; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_classifications; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_concepts; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_datasources; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_languages; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_licenses; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_oids; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_pids; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_refereed; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_sources; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_topics; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_affiliated_country; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_citations; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_classifications; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_concepts; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_datasources; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_deposited_country; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_fundercount; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_gold; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_greenoa; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_languages; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_licenses; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_oids; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_organization; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_peerreviewed; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_pids; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_projectcount; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_projects; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_refereed; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_sources; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_topics; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.rndexpediture; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.roarmap; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.software; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_citations; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_classifications; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_concepts; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_datasources; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_languages; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_licenses; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_oids; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_pids; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_refereed; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_sources; +DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_topics; + + +-- Creating the shadow database, in case it doesn't exist +CREATE database IF NOT EXISTS ${stats_db_shadow_name}; + +-- Creating new views +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.category AS SELECT * FROM ${stats_db_name}.category; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.concept AS SELECT * FROM ${stats_db_name}.concept; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.context AS SELECT * FROM ${stats_db_name}.context; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.country AS SELECT * FROM ${stats_db_name}.country; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.countrygdp AS SELECT * FROM ${stats_db_name}.countrygdp; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.creation_date AS SELECT * FROM ${stats_db_name}.creation_date; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset AS SELECT * FROM ${stats_db_name}.dataset; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_citations AS SELECT * FROM ${stats_db_name}.dataset_citations; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_classifications AS SELECT * FROM ${stats_db_name}.dataset_classifications; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_concepts AS SELECT * FROM ${stats_db_name}.dataset_concepts; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_datasources AS SELECT * FROM ${stats_db_name}.dataset_datasources; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_languages AS SELECT * FROM ${stats_db_name}.dataset_languages; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_licenses AS SELECT * FROM ${stats_db_name}.dataset_licenses; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_oids AS SELECT * FROM ${stats_db_name}.dataset_oids; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_pids AS SELECT * FROM ${stats_db_name}.dataset_pids; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_refereed AS SELECT * FROM ${stats_db_name}.dataset_refereed; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_sources AS SELECT * FROM ${stats_db_name}.dataset_sources; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_topics AS SELECT * FROM ${stats_db_name}.dataset_topics; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.datasource AS SELECT * FROM ${stats_db_name}.datasource; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.datasource_languages AS SELECT * FROM ${stats_db_name}.datasource_languages; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.datasource_oids AS SELECT * FROM ${stats_db_name}.datasource_oids; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.datasource_organizations AS SELECT * FROM ${stats_db_name}.datasource_organizations; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.datasource_results AS SELECT * FROM ${stats_db_name}.datasource_results; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.datasource_sources AS SELECT * FROM ${stats_db_name}.datasource_sources; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.funder AS SELECT * FROM ${stats_db_name}.funder; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.fundref AS SELECT * FROM ${stats_db_name}.fundref; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.numbers_country AS SELECT * FROM ${stats_db_name}.numbers_country; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.organization AS SELECT * FROM ${stats_db_name}.organization; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.organization_datasources AS SELECT * FROM ${stats_db_name}.organization_datasources; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.organization_pids AS SELECT * FROM ${stats_db_name}.organization_pids; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.organization_projects AS SELECT * FROM ${stats_db_name}.organization_projects; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.organization_sources AS SELECT * FROM ${stats_db_name}.organization_sources; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct AS SELECT * FROM ${stats_db_name}.otherresearchproduct; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_citations AS SELECT * FROM ${stats_db_name}.otherresearchproduct_citations; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_classifications AS SELECT * FROM ${stats_db_name}.otherresearchproduct_classifications; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_concepts AS SELECT * FROM ${stats_db_name}.otherresearchproduct_concepts; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_datasources AS SELECT * FROM ${stats_db_name}.otherresearchproduct_datasources; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_languages AS SELECT * FROM ${stats_db_name}.otherresearchproduct_languages; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_licenses AS SELECT * FROM ${stats_db_name}.otherresearchproduct_licenses; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_oids AS SELECT * FROM ${stats_db_name}.otherresearchproduct_oids; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_pids AS SELECT * FROM ${stats_db_name}.otherresearchproduct_pids; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_refereed AS SELECT * FROM ${stats_db_name}.otherresearchproduct_refereed; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_sources AS SELECT * FROM ${stats_db_name}.otherresearchproduct_sources; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_topics AS SELECT * FROM ${stats_db_name}.otherresearchproduct_topics; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.project AS SELECT * FROM ${stats_db_name}.project; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.project_oids AS SELECT * FROM ${stats_db_name}.project_oids; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.project_organizations AS SELECT * FROM ${stats_db_name}.project_organizations; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.project_results AS SELECT * FROM ${stats_db_name}.project_results; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.project_resultcount AS SELECT * FROM ${stats_db_name}.project_resultcount; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.project_results_publication AS SELECT * FROM ${stats_db_name}.project_results_publication; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication AS SELECT * FROM ${stats_db_name}.publication; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_citations AS SELECT * FROM ${stats_db_name}.publication_citations; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_classifications AS SELECT * FROM ${stats_db_name}.publication_classifications; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_concepts AS SELECT * FROM ${stats_db_name}.publication_concepts; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_datasources AS SELECT * FROM ${stats_db_name}.publication_datasources; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_languages AS SELECT * FROM ${stats_db_name}.publication_languages; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_licenses AS SELECT * FROM ${stats_db_name}.publication_licenses; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_oids AS SELECT * FROM ${stats_db_name}.publication_oids; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_pids AS SELECT * FROM ${stats_db_name}.publication_pids; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_refereed AS SELECT * FROM ${stats_db_name}.publication_refereed; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_sources AS SELECT * FROM ${stats_db_name}.publication_sources; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_topics AS SELECT * FROM ${stats_db_name}.publication_topics; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result AS SELECT * FROM ${stats_db_name}.result; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_affiliated_country AS SELECT * FROM ${stats_db_name}.result_affiliated_country; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_citations AS SELECT * FROM ${stats_db_name}.result_citations; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_classifications AS SELECT * FROM ${stats_db_name}.result_classifications; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_concepts AS SELECT * FROM ${stats_db_name}.result_concepts; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_datasources AS SELECT * FROM ${stats_db_name}.result_datasources; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_deposited_country AS SELECT * FROM ${stats_db_name}.result_deposited_country; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_fundercount AS SELECT * FROM ${stats_db_name}.result_fundercount; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_gold AS SELECT * FROM ${stats_db_name}.result_gold; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_greenoa AS SELECT * FROM ${stats_db_name}.result_greenoa; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_languages AS SELECT * FROM ${stats_db_name}.result_languages; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_licenses AS SELECT * FROM ${stats_db_name}.result_licenses; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_oids AS SELECT * FROM ${stats_db_name}.result_oids; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_organization AS SELECT * FROM ${stats_db_name}.result_organization; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_peerreviewed AS SELECT * FROM ${stats_db_name}.result_peerreviewed; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_pids AS SELECT * FROM ${stats_db_name}.result_pids; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_projectcount AS SELECT * FROM ${stats_db_name}.result_projectcount; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_projects AS SELECT * FROM ${stats_db_name}.result_projects; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_refereed AS SELECT * FROM ${stats_db_name}.result_refereed; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_sources AS SELECT * FROM ${stats_db_name}.result_sources; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_topics AS SELECT * FROM ${stats_db_name}.result_topics; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.rndexpediture AS SELECT * FROM ${stats_db_name}.rndexpediture; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.roarmap AS SELECT * FROM ${stats_db_name}.roarmap; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software AS SELECT * FROM ${stats_db_name}.software; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_citations AS SELECT * FROM ${stats_db_name}.software_citations; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_classifications AS SELECT * FROM ${stats_db_name}.software_classifications; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_concepts AS SELECT * FROM ${stats_db_name}.software_concepts; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_datasources AS SELECT * FROM ${stats_db_name}.software_datasources; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_languages AS SELECT * FROM ${stats_db_name}.software_languages; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_licenses AS SELECT * FROM ${stats_db_name}.software_licenses; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_oids AS SELECT * FROM ${stats_db_name}.software_oids; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_pids AS SELECT * FROM ${stats_db_name}.software_pids; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_refereed AS SELECT * FROM ${stats_db_name}.software_refereed; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_sources AS SELECT * FROM ${stats_db_name}.software_sources; +CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_topics AS SELECT * FROM ${stats_db_name}.software_topics; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step18.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step18.sql new file mode 100644 index 000000000..34e48a18a --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step18.sql @@ -0,0 +1,8 @@ +------------------------------------------------------ +------------------------------------------------------ +-- Impala table statistics - Needed to make the tables +-- visible for impala +------------------------------------------------------ +------------------------------------------------------ + +INVALIDATE METADATA ${stats_db_name}; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step19.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step19.sql new file mode 100644 index 000000000..34e48a18a --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step19.sql @@ -0,0 +1,8 @@ +------------------------------------------------------ +------------------------------------------------------ +-- Impala table statistics - Needed to make the tables +-- visible for impala +------------------------------------------------------ +------------------------------------------------------ + +INVALIDATE METADATA ${stats_db_name}; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step2.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step2.sql new file mode 100644 index 000000000..ba0db25be --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step2.sql @@ -0,0 +1,44 @@ +-------------------------------------------------------------- +-------------------------------------------------------------- +-- Publication table/view and Publication related tables/views +-------------------------------------------------------------- +-------------------------------------------------------------- + +-- Publication temporary table +DROP TABLE IF EXISTS ${stats_db_name}.publication_tmp; + +CREATE TABLE ${stats_db_name}.publication_tmp (id STRING, title STRING, publisher STRING, journal STRING, date STRING, year STRING, bestlicence STRING, embargo_end_date STRING, delayed BOOLEAN, authors INT, source STRING, abstract BOOLEAN, type STRING ) clustered by (id) into 100 buckets stored as orc tblproperties('transactional'='true'); + +INSERT INTO ${stats_db_name}.publication_tmp SELECT substr(p.id, 4) as id, p.title[0].value as title, p.publisher.value as publisher, p.journal.name as journal , +p.dateofacceptance.value as date, date_format(p.dateofacceptance.value,'yyyy') as year, p.bestaccessright.classname as bestlicence, +p.embargoenddate.value as embargo_end_date, false as delayed, size(p.author) as authors , concat_ws('\u003B',p.source.value) as source, +case when size(p.description) > 0 then true else false end as abstract, +'publication' as type +from ${openaire_db_name}.publication p +where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.publication_classifications AS SELECT substr(p.id, 4) as id, instancetype.classname as type from ${openaire_db_name}.publication p LATERAL VIEW explode(p.instance.instancetype) instances as instancetype where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.publication_concepts AS SELECT substr(p.id, 4) as id, contexts.context.id as concept from ${openaire_db_name}.publication p LATERAL VIEW explode(p.context) contexts as context where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.publication_datasources as +SELECT p.id, case when d.id is null then 'other' else p.datasource end as datasource + FROM ( + SELECT substr(p.id, 4) as id, substr(instances.instance.hostedby.key, 4) as datasource + from ${openaire_db_name}.publication p lateral view explode(p.instance) instances as instance + where p.datainfo.deletedbyinference=false ) p + LEFT OUTER JOIN ( + SELECT substr(d.id, 4) id + from ${openaire_db_name}.datasource d + WHERE d.datainfo.deletedbyinference=false ) d on p.datasource = d.id; + +CREATE TABLE ${stats_db_name}.publication_languages AS select substr(p.id, 4) as id, p.language.classname as language FROM ${openaire_db_name}.publication p where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.publication_oids AS SELECT substr(p.id, 4) AS id, oids.ids AS oid FROM ${openaire_db_name}.publication p LATERAL VIEW explode(p.originalid) oids AS ids where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.publication_pids AS SELECT substr(p.id, 4) AS id, ppid.qualifier.classname AS type, ppid.value as pid FROM ${openaire_db_name}.publication p LATERAL VIEW explode(p.pid) pids AS ppid where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.publication_topics as select substr(p.id, 4) AS id, subjects.subject.qualifier.classname AS TYPE, subjects.subject.value AS topic FROM ${openaire_db_name}.publication p LATERAL VIEW explode(p.subject) subjects AS subject where p.datainfo.deletedbyinference=false; + +-- Publication_citations +CREATE TABLE ${stats_db_name}.publication_citations AS SELECT substr(p.id, 4) AS id, xpath_string(citation.value, "//citation/id[@type='openaire']/@value") AS result FROM ${openaire_db_name}.publication p lateral view explode(p.extrainfo) citations AS citation WHERE xpath_string(citation.value, "//citation/id[@type='openaire']/@value") !="" and p.datainfo.deletedbyinference=false; \ No newline at end of file diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step3.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step3.sql new file mode 100644 index 000000000..f69715a31 --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step3.sql @@ -0,0 +1,36 @@ +------------------------------------------------------ +------------------------------------------------------ +-- Dataset table/view and Dataset related tables/views +------------------------------------------------------ +------------------------------------------------------ + +-- Dataset temporary table supporting updates +DROP TABLE IF EXISTS ${stats_db_name}.dataset_tmp; +CREATE TABLE ${stats_db_name}.dataset_tmp (id STRING, title STRING, publisher STRING, journal STRING, date STRING, year STRING, bestlicence STRING, embargo_end_date STRING, delayed BOOLEAN, authors INT, source STRING, abstract BOOLEAN, type STRING ) clustered by (id) into 100 buckets stored AS orc tblproperties('transactional'='true'); + +INSERT INTO ${stats_db_name}.dataset_tmp SELECT substr(d.id, 4) AS id, d.title[0].value AS title, d.publisher.value AS publisher, cast(null AS string) AS journal, +d.dateofacceptance.value as date, date_format(d.dateofacceptance.value,'yyyy') AS year, d.bestaccessright.classname AS bestlicence, +d.embargoenddate.value AS embargo_end_date, false AS delayed, size(d.author) AS authors , concat_ws('\u003B',d.source.value) AS source, + CASE WHEN SIZE(d.description) > 0 THEN TRUE ELSE FALSE end AS abstract, +'dataset' AS type +FROM ${openaire_db_name}.dataset d +WHERE d.datainfo.deletedbyinference=FALSE; + +-- Dataset_citations +CREATE TABLE ${stats_db_name}.dataset_citations AS SELECT substr(d.id, 4) AS id, xpath_string(citation.value, "//citation/id[@type='openaire']/@value") AS result FROM ${openaire_db_name}.dataset d LATERAL VIEW explode(d.extrainfo) citations AS citation WHERE xpath_string(citation.value, "//citation/id[@type='openaire']/@value") !="" and d.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.dataset_classifications AS SELECT substr(p.id, 4) AS id, instancetype.classname AS type FROM ${openaire_db_name}.dataset p LATERAL VIEW explode(p.instance.instancetype) instances AS instancetype where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.dataset_concepts AS SELECT substr(p.id, 4) as id, contexts.context.id as concept from ${openaire_db_name}.dataset p LATERAL VIEW explode(p.context) contexts as context where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.dataset_datasources AS SELECT p.id, case when d.id IS NULL THEN 'other' ELSE p.datasource END AS datasource FROM (SELECT substr(p.id, 4) as id, substr(instances.instance.hostedby.key, 4) AS datasource +FROM ${openaire_db_name}.dataset p LATERAL VIEW explode(p.instance) instances AS instance where p.datainfo.deletedbyinference=false) p LEFT OUTER JOIN +(SELECT substr(d.id, 4) id FROM ${openaire_db_name}.datasource d WHERE d.datainfo.deletedbyinference=false) d ON p.datasource = d.id; + +CREATE TABLE ${stats_db_name}.dataset_languages AS SELECT substr(p.id, 4) AS id, p.language.classname AS language FROM ${openaire_db_name}.dataset p where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.dataset_oids AS SELECT substr(p.id, 4) AS id, oids.ids AS oid FROM ${openaire_db_name}.dataset p LATERAL VIEW explode(p.originalid) oids AS ids where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.dataset_pids AS SELECT substr(p.id, 4) AS id, ppid.qualifier.classname AS type, ppid.value AS pid FROM ${openaire_db_name}.dataset p LATERAL VIEW explode(p.pid) pids AS ppid where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.dataset_topics AS SELECT substr(p.id, 4) AS id, subjects.subject.qualifier.classname AS type, subjects.subject.value AS topic FROM ${openaire_db_name}.dataset p LATERAL VIEW explode(p.subject) subjects AS subject where p.datainfo.deletedbyinference=false; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step4.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step4.sql new file mode 100644 index 000000000..2c4a625e1 --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step4.sql @@ -0,0 +1,36 @@ +-------------------------------------------------------- +-------------------------------------------------------- +-- Software table/view and Software related tables/views +-------------------------------------------------------- +-------------------------------------------------------- + +-- Software temporary table supporting updates +DROP TABLE IF EXISTS ${stats_db_name}.software_tmp; +CREATE TABLE ${stats_db_name}.software_tmp (id STRING, title STRING, publisher STRING, journal STRING, date STRING, year STRING, bestlicence STRING, embargo_end_date STRING, delayed BOOLEAN, authors INT, source STRING, abstract BOOLEAN, type STRING ) clustered by (id) INTO 100 buckets stored AS orc tblproperties('transactional'='true'); + +INSERT INTO ${stats_db_name}.software_tmp SELECT substr(s.id, 4) as id, s.title[0].value AS title, s.publisher.value AS publisher, CAST(NULL AS string) AS journal, +s.dateofacceptance.value AS DATE, date_format(s.dateofacceptance.value,'yyyy') AS YEAR, s.bestaccessright.classname AS bestlicence, +s.embargoenddate.value AS embargo_end_date, FALSE AS delayed, SIZE(s.author) AS authors , concat_ws('\u003B',s.source.value) AS source, + CASE WHEN SIZE(s.description) > 0 THEN TRUE ELSE FALSE END AS abstract, +'software' as type +from ${openaire_db_name}.software s +where s.datainfo.deletedbyinference=false; + +-- Software_citations +CREATE TABLE ${stats_db_name}.software_citations AS SELECT substr(s.id, 4) as id, xpath_string(citation.value, "//citation/id[@type='openaire']/@value") AS RESULT FROM ${openaire_db_name}.software s LATERAL VIEW explode(s.extrainfo) citations as citation where xpath_string(citation.value, "//citation/id[@type='openaire']/@value") !="" and s.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.software_classifications AS SELECT substr(p.id, 4) AS id, instancetype.classname AS type FROM ${openaire_db_name}.software p LATERAL VIEW explode(p.instance.instancetype) instances AS instancetype where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.software_concepts AS SELECT substr(p.id, 4) AS id, contexts.context.id AS concept FROM ${openaire_db_name}.software p LATERAL VIEW explode(p.context) contexts AS context where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.software_datasources AS SELECT p.id, CASE WHEN d.id IS NULL THEN 'other' ELSE p.datasource end as datasource FROM (SELECT substr(p.id, 4) AS id, substr(instances.instance.hostedby.key, 4) AS datasource +FROM ${openaire_db_name}.software p LATERAL VIEW explode(p.instance) instances AS instance where p.datainfo.deletedbyinference=false) p LEFT OUTER JOIN +(SELECT substr(d.id, 4) id FROM ${openaire_db_name}.datasource d WHERE d.datainfo.deletedbyinference=false) d ON p.datasource = d.id; + +CREATE TABLE ${stats_db_name}.software_languages AS select substr(p.id, 4) AS id, p.language.classname AS language FROM ${openaire_db_name}.software p where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.software_oids AS SELECT substr(p.id, 4) AS id, oids.ids AS oid FROM ${openaire_db_name}.software p LATERAL VIEW explode(p.originalid) oids AS ids where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.software_pids AS SELECT substr(p.id, 4) AS id, ppid.qualifier.classname AS type, ppid.value AS pid FROM ${openaire_db_name}.software p LATERAL VIEW explode(p.pid) pids AS ppid where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.software_topics AS SELECT substr(p.id, 4) AS id, subjects.subject.qualifier.classname AS type, subjects.subject.value AS topic FROM ${openaire_db_name}.software p LATERAL VIEW explode(p.subject) subjects AS subject where p.datainfo.deletedbyinference=false; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step5.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step5.sql new file mode 100644 index 000000000..1fa5df8cb --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step5.sql @@ -0,0 +1,36 @@ +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +-- Otherresearchproduct table/view and Otherresearchproduct related tables/views +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- + +-- Otherresearchproduct temporary table supporting updates +DROP TABLE IF EXISTS ${stats_db_name}.otherresearchproduct_tmp; +CREATE TABLE ${stats_db_name}.otherresearchproduct_tmp ( id STRING, title STRING, publisher STRING, journal STRING, date STRING, year STRING, bestlicence STRING, embargo_end_date STRING, delayed BOOLEAN, authors INT, source STRING, abstract BOOLEAN, type STRING ) CLUSTERED BY (id) INTO 100 buckets stored AS orc tblproperties('transactional'='true'); + +INSERT INTO ${stats_db_name}.otherresearchproduct_tmp SELECT substr(o.id, 4) AS id, o.title[0].value AS title, o.publisher.value AS publisher, CAST(NULL AS string) AS journal, +o.dateofacceptance.value AS DATE, date_format(o.dateofacceptance.value,'yyyy') AS year, o.bestaccessright.classname AS bestlicence, +o.embargoenddate.value as embargo_end_date, FALSE AS delayed, SIZE(o.author) AS authors , concat_ws('\u003B',o.source.value) AS source, +CASE WHEN SIZE(o.description) > 0 THEN TRUE ELSE FALSE END AS abstract, +'other' AS type +FROM ${openaire_db_name}.otherresearchproduct o +WHERE o.datainfo.deletedbyinference=FALSE; + +-- Otherresearchproduct_citations +CREATE TABLE ${stats_db_name}.otherresearchproduct_citations AS SELECT substr(o.id, 4) AS id, xpath_string(citation.value, "//citation/id[@type='openaire']/@value") AS RESULT FROM ${openaire_db_name}.otherresearchproduct o LATERAL VIEW explode(o.extrainfo) citations AS citation WHERE xpath_string(citation.value, "//citation/id[@type='openaire']/@value") !="" and o.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.otherresearchproduct_classifications AS SELECT substr(p.id, 4) AS id, instancetype.classname AS type FROM ${openaire_db_name}.otherresearchproduct p LATERAL VIEW explode(p.instance.instancetype) instances AS instancetype where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.otherresearchproduct_concepts AS SELECT substr(p.id, 4) AS id, contexts.context.id AS concept FROM ${openaire_db_name}.otherresearchproduct p LATERAL VIEW explode(p.context) contexts AS context where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.otherresearchproduct_datasources AS SELECT p.id, CASE WHEN d.id IS NULL THEN 'other' ELSE p.datasource END AS datasource FROM (SELECT substr(p.id, 4) AS id, substr(instances.instance.hostedby.key, 4) AS datasource +from ${openaire_db_name}.otherresearchproduct p lateral view explode(p.instance) instances as instance where p.datainfo.deletedbyinference=false) p LEFT OUTER JOIN +(SELECT substr(d.id, 4) id from ${openaire_db_name}.datasource d WHERE d.datainfo.deletedbyinference=false) d on p.datasource = d.id; + +CREATE TABLE ${stats_db_name}.otherresearchproduct_languages AS SELECT substr(p.id, 4) AS id, p.language.classname AS language FROM ${openaire_db_name}.otherresearchproduct p where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.otherresearchproduct_oids AS SELECT substr(p.id, 4) AS id, oids.ids AS oid FROM ${openaire_db_name}.otherresearchproduct p LATERAL VIEW explode(p.originalid) oids AS ids where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.otherresearchproduct_pids AS SELECT substr(p.id, 4) AS id, ppid.qualifier.classname AS type, ppid.value AS pid FROM ${openaire_db_name}.otherresearchproduct p LATERAL VIEW explode(p.pid) pids AS ppid where p.datainfo.deletedbyinference=false; + +CREATE TABLE ${stats_db_name}.otherresearchproduct_topics AS SELECT substr(p.id, 4) AS id, subjects.subject.qualifier.classname AS type, subjects.subject.value AS topic FROM ${openaire_db_name}.otherresearchproduct p LATERAL VIEW explode(p.subject) subjects AS subject where p.datainfo.deletedbyinference=false; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step6.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step6.sql new file mode 100644 index 000000000..21a944164 --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step6.sql @@ -0,0 +1,30 @@ +-- noinspection SqlNoDataSourceInspectionForFile + +------------------------------------------------------ +------------------------------------------------------ +-- Project table/view and Project related tables/views +------------------------------------------------------ +------------------------------------------------------ +-- Project_oids Table +DROP TABLE IF EXISTS ${stats_db_name}.project_oids; +CREATE TABLE ${stats_db_name}.project_oids AS SELECT substr(p.id, 4) AS id, oids.ids AS oid FROM ${openaire_db_name}.project p LATERAL VIEW explode(p.originalid) oids AS ids; + +-- Project_organizations Table +DROP TABLE IF EXISTS ${stats_db_name}.project_organizations; +CREATE TABLE ${stats_db_name}.project_organizations AS SELECT substr(r.source, 4) AS id, substr(r.target, 4) AS organization from ${openaire_db_name}.relation r WHERE r.reltype='projectOrganization'; + +-- Project_results Table +DROP TABLE IF EXISTS ${stats_db_name}.project_results; +CREATE TABLE ${stats_db_name}.project_results AS SELECT substr(r.target, 4) AS id, substr(r.source, 4) AS result FROM ${openaire_db_name}.relation r WHERE r.reltype='resultProject' and r.datainfo.deletedbyinference=false; + +-- Project table +---------------- +-- Creating and populating temporary Project table +DROP TABLE IF EXISTS ${stats_db_name}.project_tmp; +CREATE TABLE ${stats_db_name}.project_tmp (id STRING, acronym STRING, title STRING, funder STRING, funding_lvl0 STRING, funding_lvl1 STRING, funding_lvl2 STRING, ec39 STRING, type STRING, startdate STRING, enddate STRING, start_year INT, end_year INT, duration INT, haspubs STRING, numpubs INT, daysforlastpub INT, delayedpubs INT, callidentifier STRING, code STRING) CLUSTERED BY (id) INTO 100 buckets stored AS orc tblproperties('transactional'='true'); + +INSERT INTO ${stats_db_name}.project_tmp SELECT substr(p.id, 4) AS id, p.acronym.value AS acronym, p.title.value AS title, xpath_string(p.fundingtree[0].value, '//funder/name') AS funder, xpath_string(p.fundingtree[0].value, '//funding_level_0/name') AS funding_lvl0, xpath_string(p.fundingtree[0].value, '//funding_level_1/name') AS funding_lvl1, xpath_string(p.fundingtree[0].value, '//funding_level_2/name') AS funding_lvl2, p.ecsc39.value AS ec39, p.contracttype.classname AS type, p.startdate.value AS startdate, p.enddate.value AS enddate, year(p.startdate.value) AS start_year, year(p.enddate.value) AS end_year, CAST(MONTHS_BETWEEN(p.enddate.value, p.startdate.value) AS INT) AS duration, 'no' AS haspubs, 0 AS numpubs, 0 AS daysforlastpub, 0 AS delayedpubs, p.callidentifier.value AS callidentifier, p.code.value AS code FROM ${openaire_db_name}.project p WHERE p.datainfo.deletedbyinference=false; + +create table ${stats_db_name}.funder as +select distinct xpath_string(fund, '//funder/id') as id, xpath_string(fund, '//funder/name') as name, xpath_string(fund, '//funder/shortname') as shortname +from ${openaire_db_name}.project p lateral view explode(p.fundingtree.value) fundingtree as fund diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step7.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step7.sql new file mode 100644 index 000000000..7acabf1dd --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step7.sql @@ -0,0 +1,31 @@ +---------------------------------------------------- +---------------------------------------------------- +-- Result table/view and Result related tables/views +---------------------------------------------------- +---------------------------------------------------- + +-- Views on temporary tables that should be re-created in the end +CREATE OR REPLACE VIEW ${stats_db_name}.result as SELECT *, bestlicence AS access_mode FROM ${stats_db_name}.publication_tmp UNION ALL SELECT *,bestlicence AS access_mode FROM ${stats_db_name}.software_tmp UNION ALL SELECT *,bestlicence AS access_mode FROM ${stats_db_name}.dataset_tmp UNION ALL SELECT *,bestlicence AS access_mode FROM ${stats_db_name}.otherresearchproduct_tmp; + +-- Views on final tables +CREATE OR REPLACE VIEW ${stats_db_name}.result_datasources AS SELECT * FROM ${stats_db_name}.publication_datasources UNION ALL SELECT * FROM ${stats_db_name}.software_datasources UNION ALL SELECT * FROM ${stats_db_name}.dataset_datasources UNION ALL SELECT * FROM ${stats_db_name}.otherresearchproduct_datasources; + +CREATE OR REPLACE VIEW ${stats_db_name}.result_citations AS SELECT * FROM ${stats_db_name}.publication_citations UNION ALL SELECT * FROM ${stats_db_name}.software_citations UNION ALL SELECT * FROM ${stats_db_name}.dataset_citations UNION ALL SELECT * FROM ${stats_db_name}.otherresearchproduct_citations; + +CREATE OR REPLACE VIEW ${stats_db_name}.result_classifications AS SELECT * FROM ${stats_db_name}.publication_classifications UNION ALL SELECT * FROM ${stats_db_name}.software_classifications UNION ALL SELECT * FROM ${stats_db_name}.dataset_classifications UNION ALL SELECT * FROM ${stats_db_name}.otherresearchproduct_classifications; + +CREATE OR REPLACE VIEW ${stats_db_name}.result_concepts AS SELECT * FROM ${stats_db_name}.publication_concepts UNION ALL SELECT * FROM ${stats_db_name}.software_concepts UNION ALL SELECT * FROM ${stats_db_name}.dataset_concepts UNION ALL SELECT * FROM ${stats_db_name}.otherresearchproduct_concepts; + +CREATE OR REPLACE VIEW ${stats_db_name}.result_languages AS SELECT * FROM ${stats_db_name}.publication_languages UNION ALL SELECT * FROM ${stats_db_name}.software_languages UNION ALL SELECT * FROM ${stats_db_name}.dataset_languages UNION ALL SELECT * FROM ${stats_db_name}.otherresearchproduct_languages; + +CREATE OR REPLACE VIEW ${stats_db_name}.result_oids AS SELECT * FROM ${stats_db_name}.publication_oids UNION ALL SELECT * FROM ${stats_db_name}.software_oids UNION ALL SELECT * FROM ${stats_db_name}.dataset_oids UNION ALL SELECT * FROM ${stats_db_name}.otherresearchproduct_oids; + +CREATE OR REPLACE VIEW ${stats_db_name}.result_pids AS SELECT * FROM ${stats_db_name}.publication_pids UNION ALL SELECT * FROM ${stats_db_name}.software_pids UNION ALL SELECT * FROM ${stats_db_name}.dataset_pids UNION ALL SELECT * FROM ${stats_db_name}.otherresearchproduct_pids; + +CREATE OR REPLACE VIEW ${stats_db_name}.result_topics AS SELECT * FROM ${stats_db_name}.publication_topics UNION ALL SELECT * FROM ${stats_db_name}.software_topics UNION ALL SELECT * FROM ${stats_db_name}.dataset_topics UNION ALL SELECT * FROM ${stats_db_name}.otherresearchproduct_topics; + +DROP TABLE IF EXISTS ${stats_db_name}.result_organization; +CREATE TABLE ${stats_db_name}.result_organization AS SELECT substr(r.target, 4) AS id, substr(r.source, 4) AS organization FROM ${openaire_db_name}.relation r WHERE r.reltype='resultOrganization'; + +DROP TABLE IF EXISTS ${stats_db_name}.result_projects; +CREATE TABLE ${stats_db_name}.result_projects AS select pr.result AS id, pr.id AS project, datediff(p.enddate, p.startdate) AS daysfromend FROM ${stats_db_name}.result r JOIN ${stats_db_name}.project_results pr ON r.id=pr.result JOIN ${stats_db_name}.project_tmp p ON p.id=pr.id; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step8.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step8.sql new file mode 100644 index 000000000..4e13b3dd8 --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step8.sql @@ -0,0 +1,58 @@ +-- noinspection SqlNoDataSourceInspectionForFile + +------------------------------------------------------------ +------------------------------------------------------------ +-- Datasource table/view and Datasource related tables/views +------------------------------------------------------------ +------------------------------------------------------------ + +-- Datasource table creation & update +------------------------------------- +-- Creating and populating temporary datasource table +DROP TABLE IF EXISTS ${stats_db_name}.datasource_tmp; +CREATE TABLE ${stats_db_name}.datasource_tmp(`id` string, `name` STRING, `type` STRING, `dateofvalidation` STRING, `yearofvalidation` string, `harvested` BOOLEAN, `piwik_id` INT, `latitude` STRING, `longitude`STRING, `websiteurl` STRING, `compatibility` STRING) CLUSTERED BY (id) INTO 100 buckets stored AS orc tblproperties('transactional'='true'); + +-- Insert statement that takes into account the piwik_id of the openAIRE graph +INSERT INTO ${stats_db_name}.datasource_tmp +SELECT substr(d1.id, 4) AS id, officialname.value AS name, +datasourcetype.classname AS type, dateofvalidation.value AS dateofvalidation, date_format(d1.dateofvalidation.value,'yyyy') AS yearofvalidation, +FALSE AS harvested, +CASE WHEN d2.piwik_id IS NULL THEN 0 ELSE d2.piwik_id END AS piwik_id, +d1.latitude.value AS latitude, d1.longitude.value AS longitude, +d1.websiteurl.value AS websiteurl, d1.openairecompatibility.classid AS compatibility +FROM ${openaire_db_name}.datasource d1 +LEFT OUTER JOIN +(SELECT id, split(originalidd, '\\:')[1] as piwik_id +FROM ${openaire_db_name}.datasource +LATERAL VIEW EXPLODE(originalid) temp AS originalidd +WHERE originalidd like "piwik:%") AS d2 +ON d1.id = d2.id +WHERE d1.datainfo.deletedbyinference=FALSE; + +-- Updating temporary table with everything that is not based on results -> This is done with the following "dual" table. +-- Creating a temporary dual table that will be removed after the following insert +CREATE TABLE ${stats_db_name}.dual(dummy CHAR(1)); +INSERT INTO ${stats_db_name}.dual VALUES('X'); +INSERT INTO ${stats_db_name}.datasource_tmp (`id`, `name`, `type`, `dateofvalidation`, `yearofvalidation`, `harvested`, `piwik_id`, `latitude`, `longitude`, `websiteurl`, `compatibility`) +SELECT 'other', 'Other', 'Repository', NULL, NULL, false, 0, NULL, NULL, NULL, 'unknown' FROM ${stats_db_name}.dual WHERE 'other' not in (SELECT id FROM ${stats_db_name}.datasource_tmp WHERE name='Unknown Repository'); +DROP TABLE ${stats_db_name}.dual; + +UPDATE ${stats_db_name}.datasource_tmp SET name='Other' WHERE name='Unknown Repository'; +UPDATE ${stats_db_name}.datasource_tmp SET yearofvalidation=null WHERE yearofvalidation='-1'; + +DROP TABLE IF EXISTS ${stats_db_name}.datasource_languages; +CREATE TABLE ${stats_db_name}.datasource_languages AS SELECT substr(d.id, 4) AS id, langs.languages AS language FROM ${openaire_db_name}.datasource d LATERAL VIEW explode(d.odlanguages.value) langs AS languages; + +DROP TABLE IF EXISTS ${stats_db_name}.datasource_oids; +CREATE TABLE ${stats_db_name}.datasource_oids AS SELECT substr(d.id, 4) AS id, oids.ids AS oid FROM ${openaire_db_name}.datasource d LATERAL VIEW explode(d.originalid) oids AS ids; + +DROP TABLE IF EXISTS ${stats_db_name}.datasource_organizations; +CREATE TABLE ${stats_db_name}.datasource_organizations AS SELECT substr(r.target, 4) AS id, substr(r.source, 4) AS organization FROM ${openaire_db_name}.relation r WHERE r.reltype='datasourceOrganization'; + +-- datasource sources: +-- where the datasource info have been collected from. +create table if not exists ${stats_db_name}.datasource_sources AS select substr(d.id,4) as id, substr(cf.key, 4) as datasource from ${openaire_db_name}.datasource d lateral view explode(d.collectedfrom) cfrom as cf where d.datainfo.deletedbyinference=false; + +CREATE OR REPLACE VIEW ${stats_db_name}.datasource_results AS SELECT datasource AS id, id AS result FROM ${stats_db_name}.result_datasources; + + diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step9.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step9.sql new file mode 100644 index 000000000..a918e4de4 --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step9.sql @@ -0,0 +1,12 @@ +---------------------------------------------------------------- +---------------------------------------------------------------- +-- Organization table/view and Organization related tables/views +---------------------------------------------------------------- +---------------------------------------------------------------- +DROP TABLE IF EXISTS ${stats_db_name}.organization; +CREATE TABLE IF NOT EXISTS ${stats_db_name}.organization AS SELECT substr(o.id, 4) as id, o.legalname.value as name, o.legalshortname.value as legalshortname, o.country.classid as country +FROM ${openaire_db_name}.organization o WHERE o.datainfo.deletedbyinference=FALSE; + +CREATE OR REPLACE VIEW ${stats_db_name}.organization_datasources AS SELECT organization AS id, id AS datasource FROM ${stats_db_name}.datasource_organizations; + +CREATE OR REPLACE VIEW ${stats_db_name}.organization_projects AS SELECT id AS project, organization as id FROM ${stats_db_name}.project_organizations; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml new file mode 100644 index 000000000..324e6f9a1 --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml @@ -0,0 +1,302 @@ + + + + stats_db_name + the target stats database name + + + stats_db_shadow_name + the name of the shadow schema + + + stats_db_production_name + the name of the production schema + + + hive_metastore_uris + hive server metastore URIs + + + hive_jdbc_url + hive server jdbc url + + + hive_timeout + the time period, in seconds, after which Hive fails a transaction if a Hive client has not sent a hearbeat. The default value is 300 seconds. + + + + + ${jobTracker} + ${nameNode} + + + hive.metastore.uris + ${hive_metastore_uris} + + + hive.txn.timeout + ${hive_timeout} + + + + + + + + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] + + + + + ${hive_jdbc_url} + + stats_db_name=${stats_db_name} + openaire_db_name=${openaire_db_name} + + + + + + + + ${hive_jdbc_url} + + stats_db_name=${stats_db_name} + openaire_db_name=${openaire_db_name} + + + + + + + + ${hive_jdbc_url} + + stats_db_name=${stats_db_name} + openaire_db_name=${openaire_db_name} + + + + + + + + ${hive_jdbc_url} + + stats_db_name=${stats_db_name} + openaire_db_name=${openaire_db_name} + + + + + + + + ${hive_jdbc_url} + + stats_db_name=${stats_db_name} + openaire_db_name=${openaire_db_name} + + + + + + + + ${hive_jdbc_url} + + stats_db_name=${stats_db_name} + openaire_db_name=${openaire_db_name} + + + + + + + + ${hive_jdbc_url} + + stats_db_name=${stats_db_name} + openaire_db_name=${openaire_db_name} + + + + + + + + ${hive_jdbc_url} + + stats_db_name=${stats_db_name} + openaire_db_name=${openaire_db_name} + + + + + + + + ${hive_jdbc_url} + + stats_db_name=${stats_db_name} + openaire_db_name=${openaire_db_name} + + + + + + + + ${hive_jdbc_url} + + stats_db_name=${stats_db_name} + openaire_db_name=${openaire_db_name} + external_stats_db_name=${external_stats_db_name} + + + + + + + + ${hive_jdbc_url} + + stats_db_name=${stats_db_name} + openaire_db_name=${openaire_db_name} + external_stats_db_name=${external_stats_db_name} + + + + + + + + ${hive_jdbc_url} + + stats_db_name=${stats_db_name} + openaire_db_name=${openaire_db_name} + + + + + + + + ${hive_jdbc_url} + + stats_db_name=${stats_db_name} + openaire_db_name=${openaire_db_name} + + + + + + + + ${hive_jdbc_url} + + stats_db_name=${stats_db_name} + openaire_db_name=${openaire_db_name} + + + + + + + + ${hive_jdbc_url} + + stats_db_name=${stats_db_name} + openaire_db_name=${openaire_db_name} + + + + + + + + ${hive_jdbc_url} + + stats_db_name=${stats_db_name} + openaire_db_name=${openaire_db_name} + + + + + + + + ${hive_jdbc_url} + + stats_db_name=${stats_db_name} + openaire_db_name=${openaire_db_name} + + + + + + + + ${hive_jdbc_url} + + stats_db_name=${stats_db_name} + openaire_db_name=${openaire_db_name} + + + + + + + + ${hive_jdbc_url} + + stats_db_name=${stats_db_name} + stats_db_shadow_name=${stats_db_shadow_name} + + + + + + + + ${jobTracker} + ${nameNode} + impala-shell.sh + ${stats_db_name} + step18.sql + ${wf:appPath()}/scripts/step18.sql + impala-shell.sh + + + + + + + + ${jobTracker} + ${nameNode} + impala-shell.sh + ${stats_db_shadow_name} + step19.sql + ${wf:appPath()}/scripts/step19.sql + impala-shell.sh + + + + + + + + ${jobTracker} + ${nameNode} + updateCache.sh + ${stats_tool_api_url} + updateCache.sh + + + + + + + From b7f29db12663c82c697fbdb5a36a2bcca247c121 Mon Sep 17 00:00:00 2001 From: antleb Date: Wed, 2 Dec 2020 15:57:17 +0200 Subject: [PATCH 053/445] finished first implementation of wf --- ...{step18.sql => computeProductionStats.sql} | 0 .../graph/stats/oozie_app/scripts/step1.sql | 8 - .../graph/stats/oozie_app/scripts/step10.sql | 21 -- .../graph/stats/oozie_app/scripts/step11.sql | 44 ---- .../graph/stats/oozie_app/scripts/step12.sql | 38 --- .../graph/stats/oozie_app/scripts/step13.sql | 59 ----- .../graph/stats/oozie_app/scripts/step14.sql | 49 ---- .../graph/stats/oozie_app/scripts/step15.sql | 36 --- .../graph/stats/oozie_app/scripts/step16.sql | 80 ------ .../stats/oozie_app/scripts/step16_5.sql | 55 ---- .../stats/oozie_app/scripts/step16_6.sql | 32 --- .../graph/stats/oozie_app/scripts/step17.sql | 207 --------------- .../graph/stats/oozie_app/scripts/step19.sql | 8 - .../graph/stats/oozie_app/scripts/step2.sql | 44 ---- .../graph/stats/oozie_app/scripts/step3.sql | 36 --- .../graph/stats/oozie_app/scripts/step4.sql | 36 --- .../graph/stats/oozie_app/scripts/step5.sql | 36 --- .../graph/stats/oozie_app/scripts/step6.sql | 30 --- .../graph/stats/oozie_app/scripts/step7.sql | 31 --- .../graph/stats/oozie_app/scripts/step8.sql | 58 ---- .../graph/stats/oozie_app/scripts/step9.sql | 12 - .../scripts/updateProductionViews.sql | 207 +++++++++++++++ .../dhp/oa/graph/stats/oozie_app/workflow.xml | 247 +----------------- .../dhp/oa/graph/stats/oozie_app/workflow.xml | 6 +- 24 files changed, 224 insertions(+), 1156 deletions(-) rename dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/{step18.sql => computeProductionStats.sql} (100%) delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step1.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step10.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step11.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step12.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step13.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step14.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step15.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16_5.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16_6.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step17.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step19.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step2.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step3.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step4.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step5.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step6.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step7.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step8.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step9.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/updateProductionViews.sql diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step18.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/computeProductionStats.sql similarity index 100% rename from dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step18.sql rename to dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/computeProductionStats.sql diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step1.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step1.sql deleted file mode 100644 index 9697a1dc8..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step1.sql +++ /dev/null @@ -1,8 +0,0 @@ --------------------------------------------------------------- --------------------------------------------------------------- --- Stats database creation --------------------------------------------------------------- --------------------------------------------------------------- - -DROP database IF EXISTS ${stats_db_name} CASCADE; -CREATE database ${stats_db_name}; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step10.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step10.sql deleted file mode 100644 index 46ff295f4..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step10.sql +++ /dev/null @@ -1,21 +0,0 @@ ------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------- --- Tables/views from external tables/views (Fundref, Country, CountyGDP, roarmap, rndexpediture) ------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------- -CREATE OR REPLACE VIEW ${stats_db_name}.fundref AS SELECT * FROM ${external_stats_db_name}.fundref; -CREATE OR REPLACE VIEW ${stats_db_name}.country AS SELECT * FROM ${external_stats_db_name}.country; -CREATE OR REPLACE VIEW ${stats_db_name}.countrygdp AS SELECT * FROM ${external_stats_db_name}.countrygdp; -CREATE OR REPLACE VIEW ${stats_db_name}.roarmap AS SELECT * FROM ${external_stats_db_name}.roarmap; -CREATE OR REPLACE VIEW ${stats_db_name}.rndexpediture AS SELECT * FROM ${external_stats_db_name}.rndexpediture; -CREATE OR REPLACE VIEW ${stats_db_name}.context AS SELECT * FROM ${external_stats_db_name}.context; -CREATE OR REPLACE VIEW ${stats_db_name}.category AS SELECT * FROM ${external_stats_db_name}.category; -CREATE OR REPLACE VIEW ${stats_db_name}.concept AS SELECT * FROM ${external_stats_db_name}.concept; - - ------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------- --- Creation date of the database ------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------- -create table ${stats_db_name}.creation_date as select date_format(current_date(), 'dd-MM-yyyy') as date; \ No newline at end of file diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step11.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step11.sql deleted file mode 100644 index 13e141459..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step11.sql +++ /dev/null @@ -1,44 +0,0 @@ ----------------------------------------------------------------- ----------------------------------------------------------------- --- Post processing - Updates on main tables ----------------------------------------------------------------- ----------------------------------------------------------------- - ---Datasource temporary table updates -UPDATE ${stats_db_name}.datasource_tmp SET harvested='true' WHERE datasource_tmp.id IN (SELECT DISTINCT d.id FROM ${stats_db_name}.datasource_tmp d, ${stats_db_name}.result_datasources rd WHERE d.id=rd.datasource); - --- Project temporary table update and final project table creation with final updates that can not be applied to ORC tables -UPDATE ${stats_db_name}.project_tmp SET haspubs='yes' WHERE project_tmp.id IN (SELECT pr.id FROM ${stats_db_name}.project_results pr, ${stats_db_name}.result r WHERE pr.result=r.id AND r.type='publication'); - -DROP TABLE IF EXISTS ${stats_db_name}.project; -CREATE TABLE ${stats_db_name}.project stored as parquet as -SELECT p.id , p.acronym, p.title, p.funder, p.funding_lvl0, p.funding_lvl1, p.funding_lvl2, p.ec39, p.type, p.startdate, p.enddate, p.start_year, p.end_year, p.duration, -CASE WHEN prr1.id IS NULL THEN 'no' ELSE 'yes' END AS haspubs, -CASE WHEN prr1.id IS NULL THEN 0 ELSE prr1.np END AS numpubs, -CASE WHEN prr2.id IS NULL THEN 0 ELSE prr2.daysForlastPub END AS daysforlastpub, -CASE WHEN prr2.id IS NULL THEN 0 ELSE prr2.dp END AS delayedpubs, -p.callidentifier, p.code -FROM ${stats_db_name}.project_tmp p -LEFT JOIN (SELECT pr.id, count(distinct pr.result) AS np - FROM ${stats_db_name}.project_results pr INNER JOIN ${stats_db_name}.result r ON pr.result=r.id - WHERE r.type='publication' - GROUP BY pr.id) AS prr1 on prr1.id = p.id -LEFT JOIN (SELECT pp.id, max(datediff(to_date(r.date), to_date(pp.enddate)) ) AS daysForlastPub , count(distinct r.id) AS dp - FROM ${stats_db_name}.project_tmp pp, ${stats_db_name}.project_results pr, ${stats_db_name}.result r - WHERE pp.id=pr.id AND pr.result=r.id AND r.type='publication' AND datediff(to_date(r.date), to_date(pp.enddate)) > 0 - GROUP BY pp.id) AS prr2 - ON prr2.id = p.id; - --- Publication temporary table updates -UPDATE ${stats_db_name}.publication_tmp SET delayed = 'yes' WHERE publication_tmp.id IN (SELECT distinct r.id FROM stats_wf_db_obs.result r, ${stats_db_name}.project_results pr, ${stats_db_name}.project_tmp p WHERE r.id=pr.result AND pr.id=p.id AND to_date(r.date)-to_date(p.enddate) > 0); - --- Dataset temporary table updates -UPDATE ${stats_db_name}.dataset_tmp SET delayed = 'yes' WHERE dataset_tmp.id IN (SELECT distinct r.id FROM stats_wf_db_obs.result r, ${stats_db_name}.project_results pr, ${stats_db_name}.project_tmp p WHERE r.id=pr.result AND pr.id=p.id AND to_date(r.date)-to_date(p.enddate) > 0); - --- Software temporary table updates -UPDATE ${stats_db_name}.software_tmp SET delayed = 'yes' WHERE software_tmp.id IN (SELECT distinct r.id FROM ${stats_db_name}.result r, ${stats_db_name}.project_results pr, ${stats_db_name}.project_tmp p WHERE r.id=pr.result AND pr.id=p.id AND to_date(r.date)-to_date(p.enddate) > 0); - --- Oherresearchproduct temporary table updates -UPDATE ${stats_db_name}.otherresearchproduct_tmp SET delayed = 'yes' WHERE otherresearchproduct_tmp.id IN (SELECT distinct r.id FROM ${stats_db_name}.result r, ${stats_db_name}.project_results pr, ${stats_db_name}.project_tmp p WHERE r.id=pr.result AND pr.id=p.id AND to_date(r.date)-to_date(p.enddate) > 0); - -CREATE OR REPLACE VIEW ${stats_db_name}.project_results_publication AS SELECT result_projects.id AS result, result_projects.project AS project_results, result.date as resultdate, project.enddate as projectenddate, result_projects.daysfromend AS daysfromend FROM ${stats_db_name}.result_projects, ${stats_db_name}.result, ${stats_db_name}.project WHERE result_projects.id=result.id AND result.type='publication' AND project.id=result_projects.project; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step12.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step12.sql deleted file mode 100644 index 25439852e..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step12.sql +++ /dev/null @@ -1,38 +0,0 @@ ------------------------------------------------------------------------------------------------------- --- Creating parquet tables from the updated temporary tables and removing unnecessary temporary tables ------------------------------------------------------------------------------------------------------- - -DROP TABLE IF EXISTS ${stats_db_name}.datasource; -CREATE TABLE ${stats_db_name}.datasource stored AS parquet AS SELECT * FROM ${stats_db_name}.datasource_tmp; - -DROP TABLE IF EXISTS ${stats_db_name}.publication; -CREATE TABLE ${stats_db_name}.publication stored AS parquet AS SELECT * FROM ${stats_db_name}.publication_tmp; - -DROP TABLE IF EXISTS ${stats_db_name}.dataset; -CREATE TABLE ${stats_db_name}.dataset stored AS parquet AS SELECT * FROM ${stats_db_name}.dataset_tmp; - -DROP TABLE IF EXISTS ${stats_db_name}.software; -CREATE TABLE ${stats_db_name}.software stored AS parquet AS SELECT * FROM ${stats_db_name}.software_tmp; - -DROP TABLE IF EXISTS ${stats_db_name}.otherresearchproduct; -CREATE TABLE ${stats_db_name}.otherresearchproduct stored AS parquet AS SELECT * FROM ${stats_db_name}.otherresearchproduct_tmp; - -DROP TABLE ${stats_db_name}.project_tmp; -DROP TABLE ${stats_db_name}.datasource_tmp; -DROP TABLE ${stats_db_name}.publication_tmp; -DROP TABLE ${stats_db_name}.dataset_tmp; -DROP TABLE ${stats_db_name}.software_tmp; -DROP TABLE ${stats_db_name}.otherresearchproduct_tmp; - ----------------------------------------------- --- Re-creating views from final parquet tables ---------------------------------------------- - --- Result -CREATE OR REPLACE VIEW ${stats_db_name}.result AS SELECT *, bestlicence AS access_mode FROM ${stats_db_name}.publication UNION ALL SELECT *, bestlicence as access_mode FROM ${stats_db_name}.software UNION ALL SELECT *, bestlicence AS access_mode FROM ${stats_db_name}.dataset UNION ALL SELECT *, bestlicence AS access_mode FROM ${stats_db_name}.otherresearchproduct; - - -------------------------------------------------------------------------------- --- To see with Antonis if the following is needed and where it should be placed -------------------------------------------------------------------------------- -CREATE TABLE ${stats_db_name}.numbers_country AS SELECT org.country AS country, count(distinct rd.datasource) AS datasources, count(distinct r.id) AS publications FROM ${stats_db_name}.result r, ${stats_db_name}.result_datasources rd, ${stats_db_name}.datasource d, ${stats_db_name}.datasource_organizations dor, ${stats_db_name}.organization org WHERE r.id=rd.id AND rd.datasource=d.id AND d.id=dor.id AND dor.organization=org.id AND r.type='publication' AND r.bestlicence='Open Access' GROUP BY org.country; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step13.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step13.sql deleted file mode 100644 index 795770313..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step13.sql +++ /dev/null @@ -1,59 +0,0 @@ ------------------------------------------------------- ------------------------------------------------------- --- Additional relations --- --- Sources related tables/views ------------------------------------------------------- ------------------------------------------------------- -CREATE TABLE IF NOT EXISTS ${stats_db_name}.publication_sources as -SELECT p.id, case when d.id is null then 'other' else p.datasource end as datasource -FROM ( - SELECT substr(p.id, 4) as id, substr(datasource, 4) as datasource -from ${openaire_db_name}.publication p lateral view explode(p.collectedfrom.key) c as datasource) p -LEFT OUTER JOIN -( - SELECT substr(d.id, 4) id - from ${openaire_db_name}.datasource d - WHERE d.datainfo.deletedbyinference=false) d on p.datasource = d.id; - -CREATE TABLE IF NOT EXISTS ${stats_db_name}.dataset_sources as -SELECT p.id, case when d.id is null then 'other' else p.datasource end as datasource -FROM ( - SELECT substr(p.id, 4) as id, substr(datasource, 4) as datasource -from ${openaire_db_name}.dataset p lateral view explode(p.collectedfrom.key) c as datasource) p -LEFT OUTER JOIN -( - SELECT substr(d.id, 4) id - from ${openaire_db_name}.datasource d - WHERE d.datainfo.deletedbyinference=false) d on p.datasource = d.id; - -CREATE TABLE IF NOT EXISTS ${stats_db_name}.software_sources as -SELECT p.id, case when d.id is null then 'other' else p.datasource end as datasource -FROM ( - SELECT substr(p.id, 4) as id, substr(datasource, 4) as datasource -from ${openaire_db_name}.software p lateral view explode(p.collectedfrom.key) c as datasource) p -LEFT OUTER JOIN -( - SELECT substr(d.id, 4) id - from ${openaire_db_name}.datasource d - WHERE d.datainfo.deletedbyinference=false) d on p.datasource = d.id; - -CREATE TABLE IF NOT EXISTS ${stats_db_name}.otherresearchproduct_sources as -SELECT p.id, case when d.id is null then 'other' else p.datasource end as datasource -FROM ( - SELECT substr(p.id, 4) as id, substr(datasource, 4) as datasource -from ${openaire_db_name}.otherresearchproduct p lateral view explode(p.collectedfrom.key) c as datasource) p -LEFT OUTER JOIN -( - SELECT substr(d.id, 4) id - from ${openaire_db_name}.datasource d - WHERE d.datainfo.deletedbyinference=false) d on p.datasource = d.id; - -CREATE VIEW IF NOT EXISTS ${stats_db_name}.result_sources AS -SELECT * FROM ${stats_db_name}.publication_sources -UNION ALL -SELECT * FROM ${stats_db_name}.dataset_sources -UNION ALL -SELECT * FROM ${stats_db_name}.software_sources -UNION ALL -SELECT * FROM ${stats_db_name}.otherresearchproduct_sources; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step14.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step14.sql deleted file mode 100644 index 4a56b5d68..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step14.sql +++ /dev/null @@ -1,49 +0,0 @@ ------------------------------------------------------- ------------------------------------------------------- --- Additional relations --- --- Licences related tables/views ------------------------------------------------------- ------------------------------------------------------- -CREATE TABLE IF NOT EXISTS ${stats_db_name}.publication_licenses AS -SELECT substr(p.id, 4) as id, licenses.value as type -from ${openaire_db_name}.publication p LATERAL VIEW explode(p.instance.license) instances as licenses -where licenses.value is not null and licenses.value != '' and p.datainfo.deletedbyinference=false; - -CREATE TABLE IF NOT EXISTS ${stats_db_name}.dataset_licenses AS -SELECT substr(p.id, 4) as id, licenses.value as type -from ${openaire_db_name}.dataset p LATERAL VIEW explode(p.instance.license) instances as licenses -where licenses.value is not null and licenses.value != '' and p.datainfo.deletedbyinference=false; - -CREATE TABLE IF NOT EXISTS ${stats_db_name}.software_licenses AS -SELECT substr(p.id, 4) as id, licenses.value as type -from ${openaire_db_name}.software p LATERAL VIEW explode(p.instance.license) instances as licenses -where licenses.value is not null and licenses.value != '' and p.datainfo.deletedbyinference=false; - -CREATE TABLE IF NOT EXISTS ${stats_db_name}.otherresearchproduct_licenses AS -SELECT substr(p.id, 4) as id, licenses.value as type -from ${openaire_db_name}.otherresearchproduct p LATERAL VIEW explode(p.instance.license) instances as licenses -where licenses.value is not null and licenses.value != '' and p.datainfo.deletedbyinference=false; - -CREATE VIEW IF NOT EXISTS ${stats_db_name}.result_licenses AS -SELECT * FROM ${stats_db_name}.publication_licenses -UNION ALL -SELECT * FROM ${stats_db_name}.dataset_licenses -UNION ALL -SELECT * FROM ${stats_db_name}.software_licenses -UNION ALL -SELECT * FROM ${stats_db_name}.otherresearchproduct_licenses; - -CREATE TABLE IF NOT EXISTS ${stats_db_name}.organization_pids AS -select substr(o.id, 4) as id, ppid.qualifier.classname as type, ppid.value as pid -from ${openaire_db_name}.organization o lateral view explode(o.pid) pids as ppid; - -CREATE TABLE IF NOT EXISTS ${stats_db_name}.organization_sources as -SELECT o.id, case when d.id is null then 'other' else o.datasource end as datasource -FROM ( - SELECT substr(o.id, 4) as id, substr(instances.instance.key, 4) as datasource - from ${openaire_db_name}.organization o lateral view explode(o.collectedfrom) instances as instance) o - LEFT OUTER JOIN ( - SELECT substr(d.id, 4) id - from ${openaire_db_name}.datasource d - WHERE d.datainfo.deletedbyinference=false) d on o.datasource = d.id; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step15.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step15.sql deleted file mode 100644 index 60b37048b..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step15.sql +++ /dev/null @@ -1,36 +0,0 @@ ------------------------------------------------------- ------------------------------------------------------- --- Additional relations --- --- Refereed related tables/views ------------------------------------------------------- ------------------------------------------------------- - -CREATE TABLE IF NOT EXISTS ${stats_db_name}.publication_refereed as -select substr(r.id, 4) as id, inst.refereed.classname as refereed -from ${openaire_db_name}.publication r lateral view explode(r.instance) instances as inst -where r.datainfo.deletedbyinference=false; - -CREATE TABLE IF NOT EXISTS ${stats_db_name}.dataset_refereed as -select substr(r.id, 4) as id, inst.refereed.classname as refereed -from ${openaire_db_name}.dataset r lateral view explode(r.instance) instances as inst -where r.datainfo.deletedbyinference=false; - -CREATE TABLE IF NOT EXISTS ${stats_db_name}.software_refereed as -select substr(r.id, 4) as id, inst.refereed.classname as refereed -from ${openaire_db_name}.software r lateral view explode(r.instance) instances as inst -where r.datainfo.deletedbyinference=false; - -CREATE TABLE IF NOT EXISTS ${stats_db_name}.otherresearchproduct_refereed as -select substr(r.id, 4) as id, inst.refereed.classname as refereed -from ${openaire_db_name}.otherresearchproduct r lateral view explode(r.instance) instances as inst -where r.datainfo.deletedbyinference=false; - -CREATE VIEW IF NOT EXISTS ${stats_db_name}.result_refereed as -select * from ${stats_db_name}.publication_refereed -union all -select * from ${stats_db_name}.dataset_refereed -union all -select * from ${stats_db_name}.software_refereed -union all -select * from ${stats_db_name}.otherresearchproduct_refereed; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16.sql deleted file mode 100644 index 33849b960..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16.sql +++ /dev/null @@ -1,80 +0,0 @@ ----------------------------------------------------- --- Shortcuts for various definitions in stats db --- ----------------------------------------------------- - --- Peer reviewed: --- Results that have been collected from Crossref -create table ${stats_db_name}.result_peerreviewed as -with peer_reviewed as ( - select distinct r.id as id - from ${stats_db_name}.result r - join ${stats_db_name}.result_sources rs on rs.id=r.id - join ${stats_db_name}.datasource d on d.id=rs.datasource - where d.name='Crossref') -select distinct peer_reviewed.id as id, true as peer_reviewed -from peer_reviewed -union all -select distinct r.id as id, false as peer_reviewed -from ${stats_db_name}.result r -left outer join peer_reviewed pr on pr.id=r.id -where pr.id is null; - --- Green OA: --- OA results that are hosted by an Institutional repository and have NOT been harvested from a DOAJ journal. -create table ${stats_db_name}.result_greenoa as -with result_green as ( - select distinct r.id as id - from ${stats_db_name}.result r - join ${stats_db_name}.result_datasources rd on rd.id=r.id - join ${stats_db_name}.datasource d on d.id=rd.datasource - left outer join ( - select rd.id from ${stats_db_name}.result_datasources rd - join ${stats_db_name}.datasource d on rd.datasource=d.id - join ${stats_db_name}.datasource_sources sds on sds.id=d.id - join ${stats_db_name}.datasource sd on sd.id=sds.datasource - where sd.name='DOAJ-ARTICLES' - ) as doaj on doaj.id=r.id - where r.bestlicence in ('Open Access', 'Open Source') and d.type='Institutional Repository' and doaj.id is null) -select distinct result_green.id, true as green -from result_green -union all -select distinct r.id as id, false as green -from ${stats_db_name}.result r -left outer join result_green rg on rg.id=r.id -where rg.id is null; - --- GOLD OA: --- OA results that have been harvested from a DOAJ journal. -create table ${stats_db_name}.result_gold as -with result_gold as ( - select distinct r.id as id - from ${stats_db_name}.result r - join ${stats_db_name}.result_datasources rd on rd.id=r.id - join ${stats_db_name}.datasource d on d.id=rd.datasource - join ${stats_db_name}.datasource_sources sds on sds.id=d.id - join ${stats_db_name}.datasource sd on sd.id=sds.datasource - where r.type='publication' and r.bestlicence='Open Access' and sd.name='DOAJ-Articles') -select distinct result_gold.id, true as gold -from result_gold -union all -select distinct r.id, false as gold -from ${stats_db_name}.result r -where r.id not in (select id from result_gold); - --- shortcut result-country through the organization affiliation -create table ${stats_db_name}.result_affiliated_country as -select r.id as id, o.country as country -from ${stats_db_name}.result r -join ${stats_db_name}.result_organization ro on ro.id=r.id -join ${stats_db_name}.organization o on o.id=ro.organization -where o.country is not null and o.country!=''; - --- shortcut result-country through datasource of deposition -create table ${stats_db_name}.result_deposited_country as -select r.id as id, o.country as country -from ${stats_db_name}.result r -join ${stats_db_name}.result_datasources rd on rd.id=r.id -join ${stats_db_name}.datasource d on d.id=rd.datasource -join ${stats_db_name}.datasource_organizations dor on dor.id=d.id -join ${stats_db_name}.organization o on o.id=dor.organization -where o.country is not null and o.country!=''; \ No newline at end of file diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16_5.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16_5.sql deleted file mode 100644 index f737c1ea6..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16_5.sql +++ /dev/null @@ -1,55 +0,0 @@ --- replace the creation of the result view to include the boolean fields from the previous tables (green, gold, --- peer reviewed) -drop table if exists ${stats_db_name}.result_tmp; -CREATE TABLE ${stats_db_name}.result_tmp ( - id STRING, - title STRING, - publisher STRING, - journal STRING, - `date` STRING, - `year` INT, - bestlicence STRING, - access_mode STRING, - embargo_end_date STRING, - delayed BOOLEAN, - authors INT, - source STRING, - abstract BOOLEAN, - type STRING , - peer_reviewed BOOLEAN, - green BOOLEAN, - gold BOOLEAN) -clustered by (id) into 100 buckets stored as orc tblproperties('transactional'='true'); - -insert into ${stats_db_name}.result_tmp -select r.id, r.title, r.publisher, r.journal, r.`date`, date_format(r.`date`, 'yyyy'), r.bestlicence, r.bestlicence, r.embargo_end_date, r.delayed, r.authors, r.source, r.abstract, r.type, pr.peer_reviewed, green.green, gold.gold -FROM ${stats_db_name}.publication r -LEFT OUTER JOIN ${stats_db_name}.result_peerreviewed pr on pr.id=r.id -LEFT OUTER JOIN ${stats_db_name}.result_greenoa green on green.id=r.id -LEFT OUTER JOIN ${stats_db_name}.result_gold gold on gold.id=r.id; - -insert into ${stats_db_name}.result_tmp -select r.id, r.title, r.publisher, r.journal, r.`date`, date_format(r.`date`, 'yyyy'), r.bestlicence, r.bestlicence, r.embargo_end_date, r.delayed, r.authors, r.source, r.abstract, r.type, pr.peer_reviewed, green.green, gold.gold -FROM ${stats_db_name}.dataset r -LEFT OUTER JOIN ${stats_db_name}.result_peerreviewed pr on pr.id=r.id -LEFT OUTER JOIN ${stats_db_name}.result_greenoa green on green.id=r.id -LEFT OUTER JOIN ${stats_db_name}.result_gold gold on gold.id=r.id; - -insert into ${stats_db_name}.result_tmp -select r.id, r.title, r.publisher, r.journal, r.`date`, date_format(r.`date`, 'yyyy'), r.bestlicence, r.bestlicence, r.embargo_end_date, r.delayed, r.authors, r.source, r.abstract, r.type, pr.peer_reviewed, green.green, gold.gold -FROM ${stats_db_name}.software r -LEFT OUTER JOIN ${stats_db_name}.result_peerreviewed pr on pr.id=r.id -LEFT OUTER JOIN ${stats_db_name}.result_greenoa green on green.id=r.id -LEFT OUTER JOIN ${stats_db_name}.result_gold gold on gold.id=r.id; - -insert into ${stats_db_name}.result_tmp -select r.id, r.title, r.publisher, r.journal, r.`date`, date_format(r.`date`, 'yyyy'), r.bestlicence, r.bestlicence, r.embargo_end_date, r.delayed, r.authors, r.source, r.abstract, r.type, pr.peer_reviewed, green.green, gold.gold -FROM ${stats_db_name}.otherresearchproduct r -LEFT OUTER JOIN ${stats_db_name}.result_peerreviewed pr on pr.id=r.id -LEFT OUTER JOIN ${stats_db_name}.result_greenoa green on green.id=r.id -LEFT OUTER JOIN ${stats_db_name}.result_gold gold on gold.id=r.id; - -drop table if exists ${stats_db_name}.result; -drop view if exists ${stats_db_name}.result; -create table ${stats_db_name}.result stored as parquet as select * from ${stats_db_name}.result_tmp; -drop table ${stats_db_name}.result_tmp; \ No newline at end of file diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16_6.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16_6.sql deleted file mode 100644 index ced7bbc11..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step16_6.sql +++ /dev/null @@ -1,32 +0,0 @@ -------------------------------------------- ---- Extra tables, mostly used by indicators - -create table ${stats_db_name}.result_projectcount as -select r.id, count(distinct p.id) as count -from ${stats_db_name}.result r -left outer join ${stats_db_name}.result_projects rp on rp.id=r.id -left outer join ${stats_db_name}.project p on p.id=rp.project -group by r.id; - -create table ${stats_db_name}.result_fundercount as -select r.id, count(distinct p.funder) as count -from ${stats_db_name}.result r -left outer join ${stats_db_name}.result_projects rp on rp.id=r.id -left outer join ${stats_db_name}.project p on p.id=rp.project -group by r.id; - -create table ${stats_db_name}.project_resultcount as -with rcount as ( - select p.id as pid, count(distinct r.id) as `count`, r.type as type - from ${stats_db_name}.project p - left outer join ${stats_db_name}.result_projects rp on rp.project=p.id - left outer join ${stats_db_name}.result r on r.id=rp.id - group by r.type, p.id ) -select rcount.pid, sum(case when rcount.type='publication' then rcount.count else 0 end) as publications, - sum(case when rcount.type='dataset' then rcount.count else 0 end) as datasets, - sum(case when rcount.type='software' then rcount.count else 0 end) as software, - sum(case when rcount.type='other' then rcount.count else 0 end) as other -from rcount -group by rcount.pid; - -create view ${stats_db_name}.rndexpenditure as select * from stats_ext.rndexpediture \ No newline at end of file diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step17.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step17.sql deleted file mode 100644 index 5c102d014..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step17.sql +++ /dev/null @@ -1,207 +0,0 @@ ------------------------------------------------------- ------------------------------------------------------- --- Shadow schema table exchange ------------------------------------------------------- ------------------------------------------------------- - --- Dropping old views -DROP VIEW IF EXISTS ${stats_db_shadow_name}.category; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.concept; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.context; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.country; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.countrygdp; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.creation_date; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_citations; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_classifications; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_concepts; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_datasources; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_languages; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_licenses; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_oids; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_pids; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_refereed; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_sources; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.dataset_topics; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.datasource; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.datasource_languages; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.datasource_oids; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.datasource_organizations; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.datasource_results; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.datasource_sources; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.funder; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.fundref; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.numbers_country; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.organization; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.organization_datasources; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.organization_pids; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.organization_projects; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.organization_sources; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_citations; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_classifications; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_concepts; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_datasources; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_languages; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_licenses; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_oids; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_pids; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_refereed; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_sources; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.otherresearchproduct_topics; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.project; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.project_oids; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.project_organizations; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.project_results; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.project_resultcount; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.project_results_publication; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_citations; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_classifications; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_concepts; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_datasources; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_languages; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_licenses; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_oids; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_pids; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_refereed; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_sources; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.publication_topics; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_affiliated_country; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_citations; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_classifications; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_concepts; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_datasources; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_deposited_country; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_fundercount; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_gold; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_greenoa; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_languages; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_licenses; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_oids; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_organization; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_peerreviewed; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_pids; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_projectcount; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_projects; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_refereed; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_sources; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.result_topics; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.rndexpediture; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.roarmap; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.software; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_citations; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_classifications; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_concepts; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_datasources; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_languages; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_licenses; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_oids; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_pids; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_refereed; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_sources; -DROP VIEW IF EXISTS ${stats_db_shadow_name}.software_topics; - - --- Creating the shadow database, in case it doesn't exist -CREATE database IF NOT EXISTS ${stats_db_shadow_name}; - --- Creating new views -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.category AS SELECT * FROM ${stats_db_name}.category; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.concept AS SELECT * FROM ${stats_db_name}.concept; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.context AS SELECT * FROM ${stats_db_name}.context; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.country AS SELECT * FROM ${stats_db_name}.country; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.countrygdp AS SELECT * FROM ${stats_db_name}.countrygdp; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.creation_date AS SELECT * FROM ${stats_db_name}.creation_date; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset AS SELECT * FROM ${stats_db_name}.dataset; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_citations AS SELECT * FROM ${stats_db_name}.dataset_citations; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_classifications AS SELECT * FROM ${stats_db_name}.dataset_classifications; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_concepts AS SELECT * FROM ${stats_db_name}.dataset_concepts; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_datasources AS SELECT * FROM ${stats_db_name}.dataset_datasources; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_languages AS SELECT * FROM ${stats_db_name}.dataset_languages; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_licenses AS SELECT * FROM ${stats_db_name}.dataset_licenses; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_oids AS SELECT * FROM ${stats_db_name}.dataset_oids; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_pids AS SELECT * FROM ${stats_db_name}.dataset_pids; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_refereed AS SELECT * FROM ${stats_db_name}.dataset_refereed; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_sources AS SELECT * FROM ${stats_db_name}.dataset_sources; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.dataset_topics AS SELECT * FROM ${stats_db_name}.dataset_topics; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.datasource AS SELECT * FROM ${stats_db_name}.datasource; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.datasource_languages AS SELECT * FROM ${stats_db_name}.datasource_languages; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.datasource_oids AS SELECT * FROM ${stats_db_name}.datasource_oids; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.datasource_organizations AS SELECT * FROM ${stats_db_name}.datasource_organizations; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.datasource_results AS SELECT * FROM ${stats_db_name}.datasource_results; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.datasource_sources AS SELECT * FROM ${stats_db_name}.datasource_sources; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.funder AS SELECT * FROM ${stats_db_name}.funder; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.fundref AS SELECT * FROM ${stats_db_name}.fundref; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.numbers_country AS SELECT * FROM ${stats_db_name}.numbers_country; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.organization AS SELECT * FROM ${stats_db_name}.organization; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.organization_datasources AS SELECT * FROM ${stats_db_name}.organization_datasources; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.organization_pids AS SELECT * FROM ${stats_db_name}.organization_pids; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.organization_projects AS SELECT * FROM ${stats_db_name}.organization_projects; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.organization_sources AS SELECT * FROM ${stats_db_name}.organization_sources; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct AS SELECT * FROM ${stats_db_name}.otherresearchproduct; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_citations AS SELECT * FROM ${stats_db_name}.otherresearchproduct_citations; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_classifications AS SELECT * FROM ${stats_db_name}.otherresearchproduct_classifications; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_concepts AS SELECT * FROM ${stats_db_name}.otherresearchproduct_concepts; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_datasources AS SELECT * FROM ${stats_db_name}.otherresearchproduct_datasources; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_languages AS SELECT * FROM ${stats_db_name}.otherresearchproduct_languages; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_licenses AS SELECT * FROM ${stats_db_name}.otherresearchproduct_licenses; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_oids AS SELECT * FROM ${stats_db_name}.otherresearchproduct_oids; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_pids AS SELECT * FROM ${stats_db_name}.otherresearchproduct_pids; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_refereed AS SELECT * FROM ${stats_db_name}.otherresearchproduct_refereed; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_sources AS SELECT * FROM ${stats_db_name}.otherresearchproduct_sources; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.otherresearchproduct_topics AS SELECT * FROM ${stats_db_name}.otherresearchproduct_topics; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.project AS SELECT * FROM ${stats_db_name}.project; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.project_oids AS SELECT * FROM ${stats_db_name}.project_oids; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.project_organizations AS SELECT * FROM ${stats_db_name}.project_organizations; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.project_results AS SELECT * FROM ${stats_db_name}.project_results; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.project_resultcount AS SELECT * FROM ${stats_db_name}.project_resultcount; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.project_results_publication AS SELECT * FROM ${stats_db_name}.project_results_publication; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication AS SELECT * FROM ${stats_db_name}.publication; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_citations AS SELECT * FROM ${stats_db_name}.publication_citations; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_classifications AS SELECT * FROM ${stats_db_name}.publication_classifications; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_concepts AS SELECT * FROM ${stats_db_name}.publication_concepts; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_datasources AS SELECT * FROM ${stats_db_name}.publication_datasources; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_languages AS SELECT * FROM ${stats_db_name}.publication_languages; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_licenses AS SELECT * FROM ${stats_db_name}.publication_licenses; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_oids AS SELECT * FROM ${stats_db_name}.publication_oids; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_pids AS SELECT * FROM ${stats_db_name}.publication_pids; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_refereed AS SELECT * FROM ${stats_db_name}.publication_refereed; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_sources AS SELECT * FROM ${stats_db_name}.publication_sources; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.publication_topics AS SELECT * FROM ${stats_db_name}.publication_topics; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result AS SELECT * FROM ${stats_db_name}.result; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_affiliated_country AS SELECT * FROM ${stats_db_name}.result_affiliated_country; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_citations AS SELECT * FROM ${stats_db_name}.result_citations; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_classifications AS SELECT * FROM ${stats_db_name}.result_classifications; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_concepts AS SELECT * FROM ${stats_db_name}.result_concepts; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_datasources AS SELECT * FROM ${stats_db_name}.result_datasources; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_deposited_country AS SELECT * FROM ${stats_db_name}.result_deposited_country; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_fundercount AS SELECT * FROM ${stats_db_name}.result_fundercount; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_gold AS SELECT * FROM ${stats_db_name}.result_gold; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_greenoa AS SELECT * FROM ${stats_db_name}.result_greenoa; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_languages AS SELECT * FROM ${stats_db_name}.result_languages; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_licenses AS SELECT * FROM ${stats_db_name}.result_licenses; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_oids AS SELECT * FROM ${stats_db_name}.result_oids; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_organization AS SELECT * FROM ${stats_db_name}.result_organization; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_peerreviewed AS SELECT * FROM ${stats_db_name}.result_peerreviewed; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_pids AS SELECT * FROM ${stats_db_name}.result_pids; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_projectcount AS SELECT * FROM ${stats_db_name}.result_projectcount; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_projects AS SELECT * FROM ${stats_db_name}.result_projects; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_refereed AS SELECT * FROM ${stats_db_name}.result_refereed; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_sources AS SELECT * FROM ${stats_db_name}.result_sources; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.result_topics AS SELECT * FROM ${stats_db_name}.result_topics; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.rndexpediture AS SELECT * FROM ${stats_db_name}.rndexpediture; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.roarmap AS SELECT * FROM ${stats_db_name}.roarmap; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software AS SELECT * FROM ${stats_db_name}.software; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_citations AS SELECT * FROM ${stats_db_name}.software_citations; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_classifications AS SELECT * FROM ${stats_db_name}.software_classifications; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_concepts AS SELECT * FROM ${stats_db_name}.software_concepts; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_datasources AS SELECT * FROM ${stats_db_name}.software_datasources; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_languages AS SELECT * FROM ${stats_db_name}.software_languages; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_licenses AS SELECT * FROM ${stats_db_name}.software_licenses; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_oids AS SELECT * FROM ${stats_db_name}.software_oids; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_pids AS SELECT * FROM ${stats_db_name}.software_pids; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_refereed AS SELECT * FROM ${stats_db_name}.software_refereed; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_sources AS SELECT * FROM ${stats_db_name}.software_sources; -CREATE VIEW IF NOT EXISTS ${stats_db_shadow_name}.software_topics AS SELECT * FROM ${stats_db_name}.software_topics; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step19.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step19.sql deleted file mode 100644 index 34e48a18a..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step19.sql +++ /dev/null @@ -1,8 +0,0 @@ ------------------------------------------------------- ------------------------------------------------------- --- Impala table statistics - Needed to make the tables --- visible for impala ------------------------------------------------------- ------------------------------------------------------- - -INVALIDATE METADATA ${stats_db_name}; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step2.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step2.sql deleted file mode 100644 index ba0db25be..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step2.sql +++ /dev/null @@ -1,44 +0,0 @@ --------------------------------------------------------------- --------------------------------------------------------------- --- Publication table/view and Publication related tables/views --------------------------------------------------------------- --------------------------------------------------------------- - --- Publication temporary table -DROP TABLE IF EXISTS ${stats_db_name}.publication_tmp; - -CREATE TABLE ${stats_db_name}.publication_tmp (id STRING, title STRING, publisher STRING, journal STRING, date STRING, year STRING, bestlicence STRING, embargo_end_date STRING, delayed BOOLEAN, authors INT, source STRING, abstract BOOLEAN, type STRING ) clustered by (id) into 100 buckets stored as orc tblproperties('transactional'='true'); - -INSERT INTO ${stats_db_name}.publication_tmp SELECT substr(p.id, 4) as id, p.title[0].value as title, p.publisher.value as publisher, p.journal.name as journal , -p.dateofacceptance.value as date, date_format(p.dateofacceptance.value,'yyyy') as year, p.bestaccessright.classname as bestlicence, -p.embargoenddate.value as embargo_end_date, false as delayed, size(p.author) as authors , concat_ws('\u003B',p.source.value) as source, -case when size(p.description) > 0 then true else false end as abstract, -'publication' as type -from ${openaire_db_name}.publication p -where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.publication_classifications AS SELECT substr(p.id, 4) as id, instancetype.classname as type from ${openaire_db_name}.publication p LATERAL VIEW explode(p.instance.instancetype) instances as instancetype where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.publication_concepts AS SELECT substr(p.id, 4) as id, contexts.context.id as concept from ${openaire_db_name}.publication p LATERAL VIEW explode(p.context) contexts as context where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.publication_datasources as -SELECT p.id, case when d.id is null then 'other' else p.datasource end as datasource - FROM ( - SELECT substr(p.id, 4) as id, substr(instances.instance.hostedby.key, 4) as datasource - from ${openaire_db_name}.publication p lateral view explode(p.instance) instances as instance - where p.datainfo.deletedbyinference=false ) p - LEFT OUTER JOIN ( - SELECT substr(d.id, 4) id - from ${openaire_db_name}.datasource d - WHERE d.datainfo.deletedbyinference=false ) d on p.datasource = d.id; - -CREATE TABLE ${stats_db_name}.publication_languages AS select substr(p.id, 4) as id, p.language.classname as language FROM ${openaire_db_name}.publication p where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.publication_oids AS SELECT substr(p.id, 4) AS id, oids.ids AS oid FROM ${openaire_db_name}.publication p LATERAL VIEW explode(p.originalid) oids AS ids where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.publication_pids AS SELECT substr(p.id, 4) AS id, ppid.qualifier.classname AS type, ppid.value as pid FROM ${openaire_db_name}.publication p LATERAL VIEW explode(p.pid) pids AS ppid where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.publication_topics as select substr(p.id, 4) AS id, subjects.subject.qualifier.classname AS TYPE, subjects.subject.value AS topic FROM ${openaire_db_name}.publication p LATERAL VIEW explode(p.subject) subjects AS subject where p.datainfo.deletedbyinference=false; - --- Publication_citations -CREATE TABLE ${stats_db_name}.publication_citations AS SELECT substr(p.id, 4) AS id, xpath_string(citation.value, "//citation/id[@type='openaire']/@value") AS result FROM ${openaire_db_name}.publication p lateral view explode(p.extrainfo) citations AS citation WHERE xpath_string(citation.value, "//citation/id[@type='openaire']/@value") !="" and p.datainfo.deletedbyinference=false; \ No newline at end of file diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step3.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step3.sql deleted file mode 100644 index f69715a31..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step3.sql +++ /dev/null @@ -1,36 +0,0 @@ ------------------------------------------------------- ------------------------------------------------------- --- Dataset table/view and Dataset related tables/views ------------------------------------------------------- ------------------------------------------------------- - --- Dataset temporary table supporting updates -DROP TABLE IF EXISTS ${stats_db_name}.dataset_tmp; -CREATE TABLE ${stats_db_name}.dataset_tmp (id STRING, title STRING, publisher STRING, journal STRING, date STRING, year STRING, bestlicence STRING, embargo_end_date STRING, delayed BOOLEAN, authors INT, source STRING, abstract BOOLEAN, type STRING ) clustered by (id) into 100 buckets stored AS orc tblproperties('transactional'='true'); - -INSERT INTO ${stats_db_name}.dataset_tmp SELECT substr(d.id, 4) AS id, d.title[0].value AS title, d.publisher.value AS publisher, cast(null AS string) AS journal, -d.dateofacceptance.value as date, date_format(d.dateofacceptance.value,'yyyy') AS year, d.bestaccessright.classname AS bestlicence, -d.embargoenddate.value AS embargo_end_date, false AS delayed, size(d.author) AS authors , concat_ws('\u003B',d.source.value) AS source, - CASE WHEN SIZE(d.description) > 0 THEN TRUE ELSE FALSE end AS abstract, -'dataset' AS type -FROM ${openaire_db_name}.dataset d -WHERE d.datainfo.deletedbyinference=FALSE; - --- Dataset_citations -CREATE TABLE ${stats_db_name}.dataset_citations AS SELECT substr(d.id, 4) AS id, xpath_string(citation.value, "//citation/id[@type='openaire']/@value") AS result FROM ${openaire_db_name}.dataset d LATERAL VIEW explode(d.extrainfo) citations AS citation WHERE xpath_string(citation.value, "//citation/id[@type='openaire']/@value") !="" and d.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.dataset_classifications AS SELECT substr(p.id, 4) AS id, instancetype.classname AS type FROM ${openaire_db_name}.dataset p LATERAL VIEW explode(p.instance.instancetype) instances AS instancetype where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.dataset_concepts AS SELECT substr(p.id, 4) as id, contexts.context.id as concept from ${openaire_db_name}.dataset p LATERAL VIEW explode(p.context) contexts as context where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.dataset_datasources AS SELECT p.id, case when d.id IS NULL THEN 'other' ELSE p.datasource END AS datasource FROM (SELECT substr(p.id, 4) as id, substr(instances.instance.hostedby.key, 4) AS datasource -FROM ${openaire_db_name}.dataset p LATERAL VIEW explode(p.instance) instances AS instance where p.datainfo.deletedbyinference=false) p LEFT OUTER JOIN -(SELECT substr(d.id, 4) id FROM ${openaire_db_name}.datasource d WHERE d.datainfo.deletedbyinference=false) d ON p.datasource = d.id; - -CREATE TABLE ${stats_db_name}.dataset_languages AS SELECT substr(p.id, 4) AS id, p.language.classname AS language FROM ${openaire_db_name}.dataset p where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.dataset_oids AS SELECT substr(p.id, 4) AS id, oids.ids AS oid FROM ${openaire_db_name}.dataset p LATERAL VIEW explode(p.originalid) oids AS ids where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.dataset_pids AS SELECT substr(p.id, 4) AS id, ppid.qualifier.classname AS type, ppid.value AS pid FROM ${openaire_db_name}.dataset p LATERAL VIEW explode(p.pid) pids AS ppid where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.dataset_topics AS SELECT substr(p.id, 4) AS id, subjects.subject.qualifier.classname AS type, subjects.subject.value AS topic FROM ${openaire_db_name}.dataset p LATERAL VIEW explode(p.subject) subjects AS subject where p.datainfo.deletedbyinference=false; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step4.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step4.sql deleted file mode 100644 index 2c4a625e1..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step4.sql +++ /dev/null @@ -1,36 +0,0 @@ --------------------------------------------------------- --------------------------------------------------------- --- Software table/view and Software related tables/views --------------------------------------------------------- --------------------------------------------------------- - --- Software temporary table supporting updates -DROP TABLE IF EXISTS ${stats_db_name}.software_tmp; -CREATE TABLE ${stats_db_name}.software_tmp (id STRING, title STRING, publisher STRING, journal STRING, date STRING, year STRING, bestlicence STRING, embargo_end_date STRING, delayed BOOLEAN, authors INT, source STRING, abstract BOOLEAN, type STRING ) clustered by (id) INTO 100 buckets stored AS orc tblproperties('transactional'='true'); - -INSERT INTO ${stats_db_name}.software_tmp SELECT substr(s.id, 4) as id, s.title[0].value AS title, s.publisher.value AS publisher, CAST(NULL AS string) AS journal, -s.dateofacceptance.value AS DATE, date_format(s.dateofacceptance.value,'yyyy') AS YEAR, s.bestaccessright.classname AS bestlicence, -s.embargoenddate.value AS embargo_end_date, FALSE AS delayed, SIZE(s.author) AS authors , concat_ws('\u003B',s.source.value) AS source, - CASE WHEN SIZE(s.description) > 0 THEN TRUE ELSE FALSE END AS abstract, -'software' as type -from ${openaire_db_name}.software s -where s.datainfo.deletedbyinference=false; - --- Software_citations -CREATE TABLE ${stats_db_name}.software_citations AS SELECT substr(s.id, 4) as id, xpath_string(citation.value, "//citation/id[@type='openaire']/@value") AS RESULT FROM ${openaire_db_name}.software s LATERAL VIEW explode(s.extrainfo) citations as citation where xpath_string(citation.value, "//citation/id[@type='openaire']/@value") !="" and s.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.software_classifications AS SELECT substr(p.id, 4) AS id, instancetype.classname AS type FROM ${openaire_db_name}.software p LATERAL VIEW explode(p.instance.instancetype) instances AS instancetype where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.software_concepts AS SELECT substr(p.id, 4) AS id, contexts.context.id AS concept FROM ${openaire_db_name}.software p LATERAL VIEW explode(p.context) contexts AS context where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.software_datasources AS SELECT p.id, CASE WHEN d.id IS NULL THEN 'other' ELSE p.datasource end as datasource FROM (SELECT substr(p.id, 4) AS id, substr(instances.instance.hostedby.key, 4) AS datasource -FROM ${openaire_db_name}.software p LATERAL VIEW explode(p.instance) instances AS instance where p.datainfo.deletedbyinference=false) p LEFT OUTER JOIN -(SELECT substr(d.id, 4) id FROM ${openaire_db_name}.datasource d WHERE d.datainfo.deletedbyinference=false) d ON p.datasource = d.id; - -CREATE TABLE ${stats_db_name}.software_languages AS select substr(p.id, 4) AS id, p.language.classname AS language FROM ${openaire_db_name}.software p where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.software_oids AS SELECT substr(p.id, 4) AS id, oids.ids AS oid FROM ${openaire_db_name}.software p LATERAL VIEW explode(p.originalid) oids AS ids where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.software_pids AS SELECT substr(p.id, 4) AS id, ppid.qualifier.classname AS type, ppid.value AS pid FROM ${openaire_db_name}.software p LATERAL VIEW explode(p.pid) pids AS ppid where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.software_topics AS SELECT substr(p.id, 4) AS id, subjects.subject.qualifier.classname AS type, subjects.subject.value AS topic FROM ${openaire_db_name}.software p LATERAL VIEW explode(p.subject) subjects AS subject where p.datainfo.deletedbyinference=false; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step5.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step5.sql deleted file mode 100644 index 1fa5df8cb..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step5.sql +++ /dev/null @@ -1,36 +0,0 @@ --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- --- Otherresearchproduct table/view and Otherresearchproduct related tables/views --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- - --- Otherresearchproduct temporary table supporting updates -DROP TABLE IF EXISTS ${stats_db_name}.otherresearchproduct_tmp; -CREATE TABLE ${stats_db_name}.otherresearchproduct_tmp ( id STRING, title STRING, publisher STRING, journal STRING, date STRING, year STRING, bestlicence STRING, embargo_end_date STRING, delayed BOOLEAN, authors INT, source STRING, abstract BOOLEAN, type STRING ) CLUSTERED BY (id) INTO 100 buckets stored AS orc tblproperties('transactional'='true'); - -INSERT INTO ${stats_db_name}.otherresearchproduct_tmp SELECT substr(o.id, 4) AS id, o.title[0].value AS title, o.publisher.value AS publisher, CAST(NULL AS string) AS journal, -o.dateofacceptance.value AS DATE, date_format(o.dateofacceptance.value,'yyyy') AS year, o.bestaccessright.classname AS bestlicence, -o.embargoenddate.value as embargo_end_date, FALSE AS delayed, SIZE(o.author) AS authors , concat_ws('\u003B',o.source.value) AS source, -CASE WHEN SIZE(o.description) > 0 THEN TRUE ELSE FALSE END AS abstract, -'other' AS type -FROM ${openaire_db_name}.otherresearchproduct o -WHERE o.datainfo.deletedbyinference=FALSE; - --- Otherresearchproduct_citations -CREATE TABLE ${stats_db_name}.otherresearchproduct_citations AS SELECT substr(o.id, 4) AS id, xpath_string(citation.value, "//citation/id[@type='openaire']/@value") AS RESULT FROM ${openaire_db_name}.otherresearchproduct o LATERAL VIEW explode(o.extrainfo) citations AS citation WHERE xpath_string(citation.value, "//citation/id[@type='openaire']/@value") !="" and o.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.otherresearchproduct_classifications AS SELECT substr(p.id, 4) AS id, instancetype.classname AS type FROM ${openaire_db_name}.otherresearchproduct p LATERAL VIEW explode(p.instance.instancetype) instances AS instancetype where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.otherresearchproduct_concepts AS SELECT substr(p.id, 4) AS id, contexts.context.id AS concept FROM ${openaire_db_name}.otherresearchproduct p LATERAL VIEW explode(p.context) contexts AS context where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.otherresearchproduct_datasources AS SELECT p.id, CASE WHEN d.id IS NULL THEN 'other' ELSE p.datasource END AS datasource FROM (SELECT substr(p.id, 4) AS id, substr(instances.instance.hostedby.key, 4) AS datasource -from ${openaire_db_name}.otherresearchproduct p lateral view explode(p.instance) instances as instance where p.datainfo.deletedbyinference=false) p LEFT OUTER JOIN -(SELECT substr(d.id, 4) id from ${openaire_db_name}.datasource d WHERE d.datainfo.deletedbyinference=false) d on p.datasource = d.id; - -CREATE TABLE ${stats_db_name}.otherresearchproduct_languages AS SELECT substr(p.id, 4) AS id, p.language.classname AS language FROM ${openaire_db_name}.otherresearchproduct p where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.otherresearchproduct_oids AS SELECT substr(p.id, 4) AS id, oids.ids AS oid FROM ${openaire_db_name}.otherresearchproduct p LATERAL VIEW explode(p.originalid) oids AS ids where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.otherresearchproduct_pids AS SELECT substr(p.id, 4) AS id, ppid.qualifier.classname AS type, ppid.value AS pid FROM ${openaire_db_name}.otherresearchproduct p LATERAL VIEW explode(p.pid) pids AS ppid where p.datainfo.deletedbyinference=false; - -CREATE TABLE ${stats_db_name}.otherresearchproduct_topics AS SELECT substr(p.id, 4) AS id, subjects.subject.qualifier.classname AS type, subjects.subject.value AS topic FROM ${openaire_db_name}.otherresearchproduct p LATERAL VIEW explode(p.subject) subjects AS subject where p.datainfo.deletedbyinference=false; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step6.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step6.sql deleted file mode 100644 index 21a944164..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step6.sql +++ /dev/null @@ -1,30 +0,0 @@ --- noinspection SqlNoDataSourceInspectionForFile - ------------------------------------------------------- ------------------------------------------------------- --- Project table/view and Project related tables/views ------------------------------------------------------- ------------------------------------------------------- --- Project_oids Table -DROP TABLE IF EXISTS ${stats_db_name}.project_oids; -CREATE TABLE ${stats_db_name}.project_oids AS SELECT substr(p.id, 4) AS id, oids.ids AS oid FROM ${openaire_db_name}.project p LATERAL VIEW explode(p.originalid) oids AS ids; - --- Project_organizations Table -DROP TABLE IF EXISTS ${stats_db_name}.project_organizations; -CREATE TABLE ${stats_db_name}.project_organizations AS SELECT substr(r.source, 4) AS id, substr(r.target, 4) AS organization from ${openaire_db_name}.relation r WHERE r.reltype='projectOrganization'; - --- Project_results Table -DROP TABLE IF EXISTS ${stats_db_name}.project_results; -CREATE TABLE ${stats_db_name}.project_results AS SELECT substr(r.target, 4) AS id, substr(r.source, 4) AS result FROM ${openaire_db_name}.relation r WHERE r.reltype='resultProject' and r.datainfo.deletedbyinference=false; - --- Project table ----------------- --- Creating and populating temporary Project table -DROP TABLE IF EXISTS ${stats_db_name}.project_tmp; -CREATE TABLE ${stats_db_name}.project_tmp (id STRING, acronym STRING, title STRING, funder STRING, funding_lvl0 STRING, funding_lvl1 STRING, funding_lvl2 STRING, ec39 STRING, type STRING, startdate STRING, enddate STRING, start_year INT, end_year INT, duration INT, haspubs STRING, numpubs INT, daysforlastpub INT, delayedpubs INT, callidentifier STRING, code STRING) CLUSTERED BY (id) INTO 100 buckets stored AS orc tblproperties('transactional'='true'); - -INSERT INTO ${stats_db_name}.project_tmp SELECT substr(p.id, 4) AS id, p.acronym.value AS acronym, p.title.value AS title, xpath_string(p.fundingtree[0].value, '//funder/name') AS funder, xpath_string(p.fundingtree[0].value, '//funding_level_0/name') AS funding_lvl0, xpath_string(p.fundingtree[0].value, '//funding_level_1/name') AS funding_lvl1, xpath_string(p.fundingtree[0].value, '//funding_level_2/name') AS funding_lvl2, p.ecsc39.value AS ec39, p.contracttype.classname AS type, p.startdate.value AS startdate, p.enddate.value AS enddate, year(p.startdate.value) AS start_year, year(p.enddate.value) AS end_year, CAST(MONTHS_BETWEEN(p.enddate.value, p.startdate.value) AS INT) AS duration, 'no' AS haspubs, 0 AS numpubs, 0 AS daysforlastpub, 0 AS delayedpubs, p.callidentifier.value AS callidentifier, p.code.value AS code FROM ${openaire_db_name}.project p WHERE p.datainfo.deletedbyinference=false; - -create table ${stats_db_name}.funder as -select distinct xpath_string(fund, '//funder/id') as id, xpath_string(fund, '//funder/name') as name, xpath_string(fund, '//funder/shortname') as shortname -from ${openaire_db_name}.project p lateral view explode(p.fundingtree.value) fundingtree as fund diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step7.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step7.sql deleted file mode 100644 index 7acabf1dd..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step7.sql +++ /dev/null @@ -1,31 +0,0 @@ ----------------------------------------------------- ----------------------------------------------------- --- Result table/view and Result related tables/views ----------------------------------------------------- ----------------------------------------------------- - --- Views on temporary tables that should be re-created in the end -CREATE OR REPLACE VIEW ${stats_db_name}.result as SELECT *, bestlicence AS access_mode FROM ${stats_db_name}.publication_tmp UNION ALL SELECT *,bestlicence AS access_mode FROM ${stats_db_name}.software_tmp UNION ALL SELECT *,bestlicence AS access_mode FROM ${stats_db_name}.dataset_tmp UNION ALL SELECT *,bestlicence AS access_mode FROM ${stats_db_name}.otherresearchproduct_tmp; - --- Views on final tables -CREATE OR REPLACE VIEW ${stats_db_name}.result_datasources AS SELECT * FROM ${stats_db_name}.publication_datasources UNION ALL SELECT * FROM ${stats_db_name}.software_datasources UNION ALL SELECT * FROM ${stats_db_name}.dataset_datasources UNION ALL SELECT * FROM ${stats_db_name}.otherresearchproduct_datasources; - -CREATE OR REPLACE VIEW ${stats_db_name}.result_citations AS SELECT * FROM ${stats_db_name}.publication_citations UNION ALL SELECT * FROM ${stats_db_name}.software_citations UNION ALL SELECT * FROM ${stats_db_name}.dataset_citations UNION ALL SELECT * FROM ${stats_db_name}.otherresearchproduct_citations; - -CREATE OR REPLACE VIEW ${stats_db_name}.result_classifications AS SELECT * FROM ${stats_db_name}.publication_classifications UNION ALL SELECT * FROM ${stats_db_name}.software_classifications UNION ALL SELECT * FROM ${stats_db_name}.dataset_classifications UNION ALL SELECT * FROM ${stats_db_name}.otherresearchproduct_classifications; - -CREATE OR REPLACE VIEW ${stats_db_name}.result_concepts AS SELECT * FROM ${stats_db_name}.publication_concepts UNION ALL SELECT * FROM ${stats_db_name}.software_concepts UNION ALL SELECT * FROM ${stats_db_name}.dataset_concepts UNION ALL SELECT * FROM ${stats_db_name}.otherresearchproduct_concepts; - -CREATE OR REPLACE VIEW ${stats_db_name}.result_languages AS SELECT * FROM ${stats_db_name}.publication_languages UNION ALL SELECT * FROM ${stats_db_name}.software_languages UNION ALL SELECT * FROM ${stats_db_name}.dataset_languages UNION ALL SELECT * FROM ${stats_db_name}.otherresearchproduct_languages; - -CREATE OR REPLACE VIEW ${stats_db_name}.result_oids AS SELECT * FROM ${stats_db_name}.publication_oids UNION ALL SELECT * FROM ${stats_db_name}.software_oids UNION ALL SELECT * FROM ${stats_db_name}.dataset_oids UNION ALL SELECT * FROM ${stats_db_name}.otherresearchproduct_oids; - -CREATE OR REPLACE VIEW ${stats_db_name}.result_pids AS SELECT * FROM ${stats_db_name}.publication_pids UNION ALL SELECT * FROM ${stats_db_name}.software_pids UNION ALL SELECT * FROM ${stats_db_name}.dataset_pids UNION ALL SELECT * FROM ${stats_db_name}.otherresearchproduct_pids; - -CREATE OR REPLACE VIEW ${stats_db_name}.result_topics AS SELECT * FROM ${stats_db_name}.publication_topics UNION ALL SELECT * FROM ${stats_db_name}.software_topics UNION ALL SELECT * FROM ${stats_db_name}.dataset_topics UNION ALL SELECT * FROM ${stats_db_name}.otherresearchproduct_topics; - -DROP TABLE IF EXISTS ${stats_db_name}.result_organization; -CREATE TABLE ${stats_db_name}.result_organization AS SELECT substr(r.target, 4) AS id, substr(r.source, 4) AS organization FROM ${openaire_db_name}.relation r WHERE r.reltype='resultOrganization'; - -DROP TABLE IF EXISTS ${stats_db_name}.result_projects; -CREATE TABLE ${stats_db_name}.result_projects AS select pr.result AS id, pr.id AS project, datediff(p.enddate, p.startdate) AS daysfromend FROM ${stats_db_name}.result r JOIN ${stats_db_name}.project_results pr ON r.id=pr.result JOIN ${stats_db_name}.project_tmp p ON p.id=pr.id; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step8.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step8.sql deleted file mode 100644 index 4e13b3dd8..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step8.sql +++ /dev/null @@ -1,58 +0,0 @@ --- noinspection SqlNoDataSourceInspectionForFile - ------------------------------------------------------------- ------------------------------------------------------------- --- Datasource table/view and Datasource related tables/views ------------------------------------------------------------- ------------------------------------------------------------- - --- Datasource table creation & update -------------------------------------- --- Creating and populating temporary datasource table -DROP TABLE IF EXISTS ${stats_db_name}.datasource_tmp; -CREATE TABLE ${stats_db_name}.datasource_tmp(`id` string, `name` STRING, `type` STRING, `dateofvalidation` STRING, `yearofvalidation` string, `harvested` BOOLEAN, `piwik_id` INT, `latitude` STRING, `longitude`STRING, `websiteurl` STRING, `compatibility` STRING) CLUSTERED BY (id) INTO 100 buckets stored AS orc tblproperties('transactional'='true'); - --- Insert statement that takes into account the piwik_id of the openAIRE graph -INSERT INTO ${stats_db_name}.datasource_tmp -SELECT substr(d1.id, 4) AS id, officialname.value AS name, -datasourcetype.classname AS type, dateofvalidation.value AS dateofvalidation, date_format(d1.dateofvalidation.value,'yyyy') AS yearofvalidation, -FALSE AS harvested, -CASE WHEN d2.piwik_id IS NULL THEN 0 ELSE d2.piwik_id END AS piwik_id, -d1.latitude.value AS latitude, d1.longitude.value AS longitude, -d1.websiteurl.value AS websiteurl, d1.openairecompatibility.classid AS compatibility -FROM ${openaire_db_name}.datasource d1 -LEFT OUTER JOIN -(SELECT id, split(originalidd, '\\:')[1] as piwik_id -FROM ${openaire_db_name}.datasource -LATERAL VIEW EXPLODE(originalid) temp AS originalidd -WHERE originalidd like "piwik:%") AS d2 -ON d1.id = d2.id -WHERE d1.datainfo.deletedbyinference=FALSE; - --- Updating temporary table with everything that is not based on results -> This is done with the following "dual" table. --- Creating a temporary dual table that will be removed after the following insert -CREATE TABLE ${stats_db_name}.dual(dummy CHAR(1)); -INSERT INTO ${stats_db_name}.dual VALUES('X'); -INSERT INTO ${stats_db_name}.datasource_tmp (`id`, `name`, `type`, `dateofvalidation`, `yearofvalidation`, `harvested`, `piwik_id`, `latitude`, `longitude`, `websiteurl`, `compatibility`) -SELECT 'other', 'Other', 'Repository', NULL, NULL, false, 0, NULL, NULL, NULL, 'unknown' FROM ${stats_db_name}.dual WHERE 'other' not in (SELECT id FROM ${stats_db_name}.datasource_tmp WHERE name='Unknown Repository'); -DROP TABLE ${stats_db_name}.dual; - -UPDATE ${stats_db_name}.datasource_tmp SET name='Other' WHERE name='Unknown Repository'; -UPDATE ${stats_db_name}.datasource_tmp SET yearofvalidation=null WHERE yearofvalidation='-1'; - -DROP TABLE IF EXISTS ${stats_db_name}.datasource_languages; -CREATE TABLE ${stats_db_name}.datasource_languages AS SELECT substr(d.id, 4) AS id, langs.languages AS language FROM ${openaire_db_name}.datasource d LATERAL VIEW explode(d.odlanguages.value) langs AS languages; - -DROP TABLE IF EXISTS ${stats_db_name}.datasource_oids; -CREATE TABLE ${stats_db_name}.datasource_oids AS SELECT substr(d.id, 4) AS id, oids.ids AS oid FROM ${openaire_db_name}.datasource d LATERAL VIEW explode(d.originalid) oids AS ids; - -DROP TABLE IF EXISTS ${stats_db_name}.datasource_organizations; -CREATE TABLE ${stats_db_name}.datasource_organizations AS SELECT substr(r.target, 4) AS id, substr(r.source, 4) AS organization FROM ${openaire_db_name}.relation r WHERE r.reltype='datasourceOrganization'; - --- datasource sources: --- where the datasource info have been collected from. -create table if not exists ${stats_db_name}.datasource_sources AS select substr(d.id,4) as id, substr(cf.key, 4) as datasource from ${openaire_db_name}.datasource d lateral view explode(d.collectedfrom) cfrom as cf where d.datainfo.deletedbyinference=false; - -CREATE OR REPLACE VIEW ${stats_db_name}.datasource_results AS SELECT datasource AS id, id AS result FROM ${stats_db_name}.result_datasources; - - diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step9.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step9.sql deleted file mode 100644 index a918e4de4..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step9.sql +++ /dev/null @@ -1,12 +0,0 @@ ----------------------------------------------------------------- ----------------------------------------------------------------- --- Organization table/view and Organization related tables/views ----------------------------------------------------------------- ----------------------------------------------------------------- -DROP TABLE IF EXISTS ${stats_db_name}.organization; -CREATE TABLE IF NOT EXISTS ${stats_db_name}.organization AS SELECT substr(o.id, 4) as id, o.legalname.value as name, o.legalshortname.value as legalshortname, o.country.classid as country -FROM ${openaire_db_name}.organization o WHERE o.datainfo.deletedbyinference=FALSE; - -CREATE OR REPLACE VIEW ${stats_db_name}.organization_datasources AS SELECT organization AS id, id AS datasource FROM ${stats_db_name}.datasource_organizations; - -CREATE OR REPLACE VIEW ${stats_db_name}.organization_projects AS SELECT id AS project, organization as id FROM ${stats_db_name}.project_organizations; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/updateProductionViews.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/updateProductionViews.sql new file mode 100644 index 000000000..48f8d58fd --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/updateProductionViews.sql @@ -0,0 +1,207 @@ +------------------------------------------------------ +------------------------------------------------------ +-- Shadow schema table exchange +------------------------------------------------------ +------------------------------------------------------ + +-- Dropping old views +DROP VIEW IF EXISTS ${stats_db_production_name}.category; +DROP VIEW IF EXISTS ${stats_db_production_name}.concept; +DROP VIEW IF EXISTS ${stats_db_production_name}.context; +DROP VIEW IF EXISTS ${stats_db_production_name}.country; +DROP VIEW IF EXISTS ${stats_db_production_name}.countrygdp; +DROP VIEW IF EXISTS ${stats_db_production_name}.creation_date; +DROP VIEW IF EXISTS ${stats_db_production_name}.dataset; +DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_citations; +DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_classifications; +DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_concepts; +DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_datasources; +DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_languages; +DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_licenses; +DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_oids; +DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_pids; +DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_refereed; +DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_sources; +DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_topics; +DROP VIEW IF EXISTS ${stats_db_production_name}.datasource; +DROP VIEW IF EXISTS ${stats_db_production_name}.datasource_languages; +DROP VIEW IF EXISTS ${stats_db_production_name}.datasource_oids; +DROP VIEW IF EXISTS ${stats_db_production_name}.datasource_organizations; +DROP VIEW IF EXISTS ${stats_db_production_name}.datasource_results; +DROP VIEW IF EXISTS ${stats_db_production_name}.datasource_sources; +DROP VIEW IF EXISTS ${stats_db_production_name}.funder; +DROP VIEW IF EXISTS ${stats_db_production_name}.fundref; +DROP VIEW IF EXISTS ${stats_db_production_name}.numbers_country; +DROP VIEW IF EXISTS ${stats_db_production_name}.organization; +DROP VIEW IF EXISTS ${stats_db_production_name}.organization_datasources; +DROP VIEW IF EXISTS ${stats_db_production_name}.organization_pids; +DROP VIEW IF EXISTS ${stats_db_production_name}.organization_projects; +DROP VIEW IF EXISTS ${stats_db_production_name}.organization_sources; +DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct; +DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_citations; +DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_classifications; +DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_concepts; +DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_datasources; +DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_languages; +DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_licenses; +DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_oids; +DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_pids; +DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_refereed; +DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_sources; +DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_topics; +DROP VIEW IF EXISTS ${stats_db_production_name}.project; +DROP VIEW IF EXISTS ${stats_db_production_name}.project_oids; +DROP VIEW IF EXISTS ${stats_db_production_name}.project_organizations; +DROP VIEW IF EXISTS ${stats_db_production_name}.project_results; +DROP VIEW IF EXISTS ${stats_db_production_name}.project_resultcount; +DROP VIEW IF EXISTS ${stats_db_production_name}.project_results_publication; +DROP VIEW IF EXISTS ${stats_db_production_name}.publication; +DROP VIEW IF EXISTS ${stats_db_production_name}.publication_citations; +DROP VIEW IF EXISTS ${stats_db_production_name}.publication_classifications; +DROP VIEW IF EXISTS ${stats_db_production_name}.publication_concepts; +DROP VIEW IF EXISTS ${stats_db_production_name}.publication_datasources; +DROP VIEW IF EXISTS ${stats_db_production_name}.publication_languages; +DROP VIEW IF EXISTS ${stats_db_production_name}.publication_licenses; +DROP VIEW IF EXISTS ${stats_db_production_name}.publication_oids; +DROP VIEW IF EXISTS ${stats_db_production_name}.publication_pids; +DROP VIEW IF EXISTS ${stats_db_production_name}.publication_refereed; +DROP VIEW IF EXISTS ${stats_db_production_name}.publication_sources; +DROP VIEW IF EXISTS ${stats_db_production_name}.publication_topics; +DROP VIEW IF EXISTS ${stats_db_production_name}.result; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_affiliated_country; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_citations; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_classifications; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_concepts; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_datasources; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_deposited_country; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_fundercount; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_gold; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_greenoa; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_languages; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_licenses; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_oids; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_organization; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_peerreviewed; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_pids; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_projectcount; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_projects; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_refereed; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_sources; +DROP VIEW IF EXISTS ${stats_db_production_name}.result_topics; +DROP VIEW IF EXISTS ${stats_db_production_name}.rndexpediture; +DROP VIEW IF EXISTS ${stats_db_production_name}.roarmap; +DROP VIEW IF EXISTS ${stats_db_production_name}.software; +DROP VIEW IF EXISTS ${stats_db_production_name}.software_citations; +DROP VIEW IF EXISTS ${stats_db_production_name}.software_classifications; +DROP VIEW IF EXISTS ${stats_db_production_name}.software_concepts; +DROP VIEW IF EXISTS ${stats_db_production_name}.software_datasources; +DROP VIEW IF EXISTS ${stats_db_production_name}.software_languages; +DROP VIEW IF EXISTS ${stats_db_production_name}.software_licenses; +DROP VIEW IF EXISTS ${stats_db_production_name}.software_oids; +DROP VIEW IF EXISTS ${stats_db_production_name}.software_pids; +DROP VIEW IF EXISTS ${stats_db_production_name}.software_refereed; +DROP VIEW IF EXISTS ${stats_db_production_name}.software_sources; +DROP VIEW IF EXISTS ${stats_db_production_name}.software_topics; + + +-- Creating the shadow database, in case it doesn't exist +CREATE database IF NOT EXISTS ${stats_db_production_name}; + +-- Creating new views +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.category AS SELECT * FROM ${stats_db_name}.category; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.concept AS SELECT * FROM ${stats_db_name}.concept; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.context AS SELECT * FROM ${stats_db_name}.context; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.country AS SELECT * FROM ${stats_db_name}.country; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.countrygdp AS SELECT * FROM ${stats_db_name}.countrygdp; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.creation_date AS SELECT * FROM ${stats_db_name}.creation_date; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset AS SELECT * FROM ${stats_db_name}.dataset; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_citations AS SELECT * FROM ${stats_db_name}.dataset_citations; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_classifications AS SELECT * FROM ${stats_db_name}.dataset_classifications; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_concepts AS SELECT * FROM ${stats_db_name}.dataset_concepts; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_datasources AS SELECT * FROM ${stats_db_name}.dataset_datasources; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_languages AS SELECT * FROM ${stats_db_name}.dataset_languages; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_licenses AS SELECT * FROM ${stats_db_name}.dataset_licenses; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_oids AS SELECT * FROM ${stats_db_name}.dataset_oids; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_pids AS SELECT * FROM ${stats_db_name}.dataset_pids; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_refereed AS SELECT * FROM ${stats_db_name}.dataset_refereed; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_sources AS SELECT * FROM ${stats_db_name}.dataset_sources; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_topics AS SELECT * FROM ${stats_db_name}.dataset_topics; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.datasource AS SELECT * FROM ${stats_db_name}.datasource; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.datasource_languages AS SELECT * FROM ${stats_db_name}.datasource_languages; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.datasource_oids AS SELECT * FROM ${stats_db_name}.datasource_oids; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.datasource_organizations AS SELECT * FROM ${stats_db_name}.datasource_organizations; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.datasource_results AS SELECT * FROM ${stats_db_name}.datasource_results; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.datasource_sources AS SELECT * FROM ${stats_db_name}.datasource_sources; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.funder AS SELECT * FROM ${stats_db_name}.funder; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.fundref AS SELECT * FROM ${stats_db_name}.fundref; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.numbers_country AS SELECT * FROM ${stats_db_name}.numbers_country; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.organization AS SELECT * FROM ${stats_db_name}.organization; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.organization_datasources AS SELECT * FROM ${stats_db_name}.organization_datasources; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.organization_pids AS SELECT * FROM ${stats_db_name}.organization_pids; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.organization_projects AS SELECT * FROM ${stats_db_name}.organization_projects; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.organization_sources AS SELECT * FROM ${stats_db_name}.organization_sources; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct AS SELECT * FROM ${stats_db_name}.otherresearchproduct; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_citations AS SELECT * FROM ${stats_db_name}.otherresearchproduct_citations; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_classifications AS SELECT * FROM ${stats_db_name}.otherresearchproduct_classifications; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_concepts AS SELECT * FROM ${stats_db_name}.otherresearchproduct_concepts; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_datasources AS SELECT * FROM ${stats_db_name}.otherresearchproduct_datasources; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_languages AS SELECT * FROM ${stats_db_name}.otherresearchproduct_languages; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_licenses AS SELECT * FROM ${stats_db_name}.otherresearchproduct_licenses; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_oids AS SELECT * FROM ${stats_db_name}.otherresearchproduct_oids; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_pids AS SELECT * FROM ${stats_db_name}.otherresearchproduct_pids; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_refereed AS SELECT * FROM ${stats_db_name}.otherresearchproduct_refereed; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_sources AS SELECT * FROM ${stats_db_name}.otherresearchproduct_sources; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_topics AS SELECT * FROM ${stats_db_name}.otherresearchproduct_topics; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.project AS SELECT * FROM ${stats_db_name}.project; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.project_oids AS SELECT * FROM ${stats_db_name}.project_oids; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.project_organizations AS SELECT * FROM ${stats_db_name}.project_organizations; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.project_results AS SELECT * FROM ${stats_db_name}.project_results; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.project_resultcount AS SELECT * FROM ${stats_db_name}.project_resultcount; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.project_results_publication AS SELECT * FROM ${stats_db_name}.project_results_publication; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication AS SELECT * FROM ${stats_db_name}.publication; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_citations AS SELECT * FROM ${stats_db_name}.publication_citations; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_classifications AS SELECT * FROM ${stats_db_name}.publication_classifications; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_concepts AS SELECT * FROM ${stats_db_name}.publication_concepts; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_datasources AS SELECT * FROM ${stats_db_name}.publication_datasources; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_languages AS SELECT * FROM ${stats_db_name}.publication_languages; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_licenses AS SELECT * FROM ${stats_db_name}.publication_licenses; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_oids AS SELECT * FROM ${stats_db_name}.publication_oids; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_pids AS SELECT * FROM ${stats_db_name}.publication_pids; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_refereed AS SELECT * FROM ${stats_db_name}.publication_refereed; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_sources AS SELECT * FROM ${stats_db_name}.publication_sources; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_topics AS SELECT * FROM ${stats_db_name}.publication_topics; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result AS SELECT * FROM ${stats_db_name}.result; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_affiliated_country AS SELECT * FROM ${stats_db_name}.result_affiliated_country; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_citations AS SELECT * FROM ${stats_db_name}.result_citations; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_classifications AS SELECT * FROM ${stats_db_name}.result_classifications; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_concepts AS SELECT * FROM ${stats_db_name}.result_concepts; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_datasources AS SELECT * FROM ${stats_db_name}.result_datasources; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_deposited_country AS SELECT * FROM ${stats_db_name}.result_deposited_country; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_fundercount AS SELECT * FROM ${stats_db_name}.result_fundercount; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_gold AS SELECT * FROM ${stats_db_name}.result_gold; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_greenoa AS SELECT * FROM ${stats_db_name}.result_greenoa; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_languages AS SELECT * FROM ${stats_db_name}.result_languages; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_licenses AS SELECT * FROM ${stats_db_name}.result_licenses; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_oids AS SELECT * FROM ${stats_db_name}.result_oids; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_organization AS SELECT * FROM ${stats_db_name}.result_organization; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_peerreviewed AS SELECT * FROM ${stats_db_name}.result_peerreviewed; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_pids AS SELECT * FROM ${stats_db_name}.result_pids; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_projectcount AS SELECT * FROM ${stats_db_name}.result_projectcount; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_projects AS SELECT * FROM ${stats_db_name}.result_projects; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_refereed AS SELECT * FROM ${stats_db_name}.result_refereed; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_sources AS SELECT * FROM ${stats_db_name}.result_sources; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_topics AS SELECT * FROM ${stats_db_name}.result_topics; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.rndexpediture AS SELECT * FROM ${stats_db_name}.rndexpediture; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.roarmap AS SELECT * FROM ${stats_db_name}.roarmap; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software AS SELECT * FROM ${stats_db_name}.software; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_citations AS SELECT * FROM ${stats_db_name}.software_citations; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_classifications AS SELECT * FROM ${stats_db_name}.software_classifications; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_concepts AS SELECT * FROM ${stats_db_name}.software_concepts; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_datasources AS SELECT * FROM ${stats_db_name}.software_datasources; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_languages AS SELECT * FROM ${stats_db_name}.software_languages; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_licenses AS SELECT * FROM ${stats_db_name}.software_licenses; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_oids AS SELECT * FROM ${stats_db_name}.software_oids; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_pids AS SELECT * FROM ${stats_db_name}.software_pids; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_refereed AS SELECT * FROM ${stats_db_name}.software_refereed; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_sources AS SELECT * FROM ${stats_db_name}.software_sources; +CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_topics AS SELECT * FROM ${stats_db_name}.software_topics; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml index 324e6f9a1..ae2318238 100644 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml @@ -4,10 +4,6 @@ stats_db_name the target stats database name - - stats_db_shadow_name - the name of the shadow schema - stats_db_production_name the name of the production schema @@ -41,262 +37,47 @@ - + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] - + ${hive_jdbc_url} - + stats_db_name=${stats_db_name} - openaire_db_name=${openaire_db_name} + stats_db_production_name=${stats_db_production_name} - - - - - - - ${hive_jdbc_url} - - stats_db_name=${stats_db_name} - openaire_db_name=${openaire_db_name} - - - - - - - - ${hive_jdbc_url} - - stats_db_name=${stats_db_name} - openaire_db_name=${openaire_db_name} - - - - - - - - ${hive_jdbc_url} - - stats_db_name=${stats_db_name} - openaire_db_name=${openaire_db_name} - - - - - - - - ${hive_jdbc_url} - - stats_db_name=${stats_db_name} - openaire_db_name=${openaire_db_name} - - - - - - - - ${hive_jdbc_url} - - stats_db_name=${stats_db_name} - openaire_db_name=${openaire_db_name} - - - - - - - - ${hive_jdbc_url} - - stats_db_name=${stats_db_name} - openaire_db_name=${openaire_db_name} - - - - - - - - ${hive_jdbc_url} - - stats_db_name=${stats_db_name} - openaire_db_name=${openaire_db_name} - - - - - - - - ${hive_jdbc_url} - - stats_db_name=${stats_db_name} - openaire_db_name=${openaire_db_name} - - - - - - - - ${hive_jdbc_url} - - stats_db_name=${stats_db_name} - openaire_db_name=${openaire_db_name} - external_stats_db_name=${external_stats_db_name} - - - - - - - - ${hive_jdbc_url} - - stats_db_name=${stats_db_name} - openaire_db_name=${openaire_db_name} - external_stats_db_name=${external_stats_db_name} - - - - - - - - ${hive_jdbc_url} - - stats_db_name=${stats_db_name} - openaire_db_name=${openaire_db_name} - - - - - - - - ${hive_jdbc_url} - - stats_db_name=${stats_db_name} - openaire_db_name=${openaire_db_name} - - - - - - - - ${hive_jdbc_url} - - stats_db_name=${stats_db_name} - openaire_db_name=${openaire_db_name} - - - - - - - - ${hive_jdbc_url} - - stats_db_name=${stats_db_name} - openaire_db_name=${openaire_db_name} - - + - - - ${hive_jdbc_url} - - stats_db_name=${stats_db_name} - openaire_db_name=${openaire_db_name} - - - - - - - - ${hive_jdbc_url} - - stats_db_name=${stats_db_name} - openaire_db_name=${openaire_db_name} - - - - - - - - ${hive_jdbc_url} - - stats_db_name=${stats_db_name} - openaire_db_name=${openaire_db_name} - - - - - - - - ${hive_jdbc_url} - - stats_db_name=${stats_db_name} - stats_db_shadow_name=${stats_db_shadow_name} - - - - - - + ${jobTracker} ${nameNode} impala-shell.sh - ${stats_db_name} - step18.sql - ${wf:appPath()}/scripts/step18.sql + ${stats_db_production_name} + computeProductionStats.sql + ${wf:appPath()}/scripts/computeProductionStats.sql impala-shell.sh - - - - - - - ${jobTracker} - ${nameNode} - impala-shell.sh - ${stats_db_shadow_name} - step19.sql - ${wf:appPath()}/scripts/step19.sql - impala-shell.sh - - + - + ${jobTracker} ${nameNode} - updateCache.sh + promoteCache.sh ${stats_tool_api_url} - updateCache.sh + promoteCache.sh - - + \ No newline at end of file diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml index 0b6a00df1..451461669 100644 --- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml @@ -255,7 +255,7 @@ ${hive_jdbc_url} - + stats_db_name=${stats_db_name} stats_db_shadow_name=${stats_db_shadow_name} @@ -283,8 +283,8 @@ ${nameNode} impala-shell.sh ${stats_db_shadow_name} - step19.sql - ${wf:appPath()}/scripts/step19.sql + computeProductionStats.sql + ${wf:appPath()}/scripts/computeProductionStats.sql impala-shell.sh From 91226117b3a5687d3b854b77f3e05d8bc7674902 Mon Sep 17 00:00:00 2001 From: antleb Date: Fri, 4 Dec 2020 12:42:17 +0200 Subject: [PATCH 054/445] ignoring deletedbyinference relations --- .../eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step6.sql | 2 +- .../eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step7.sql | 2 +- .../eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step8.sql | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step6.sql b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step6.sql index 21a944164..b4745535d 100644 --- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step6.sql +++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step6.sql @@ -11,7 +11,7 @@ CREATE TABLE ${stats_db_name}.project_oids AS SELECT substr(p.id, 4) AS id, oids -- Project_organizations Table DROP TABLE IF EXISTS ${stats_db_name}.project_organizations; -CREATE TABLE ${stats_db_name}.project_organizations AS SELECT substr(r.source, 4) AS id, substr(r.target, 4) AS organization from ${openaire_db_name}.relation r WHERE r.reltype='projectOrganization'; +CREATE TABLE ${stats_db_name}.project_organizations AS SELECT substr(r.source, 4) AS id, substr(r.target, 4) AS organization from ${openaire_db_name}.relation r WHERE r.reltype='projectOrganization' and r.datainfo.deletedbyinference=false; -- Project_results Table DROP TABLE IF EXISTS ${stats_db_name}.project_results; diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step7.sql b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step7.sql index 7acabf1dd..36a4a8a49 100644 --- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step7.sql +++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step7.sql @@ -25,7 +25,7 @@ CREATE OR REPLACE VIEW ${stats_db_name}.result_pids AS SELECT * FROM ${stats_db_ CREATE OR REPLACE VIEW ${stats_db_name}.result_topics AS SELECT * FROM ${stats_db_name}.publication_topics UNION ALL SELECT * FROM ${stats_db_name}.software_topics UNION ALL SELECT * FROM ${stats_db_name}.dataset_topics UNION ALL SELECT * FROM ${stats_db_name}.otherresearchproduct_topics; DROP TABLE IF EXISTS ${stats_db_name}.result_organization; -CREATE TABLE ${stats_db_name}.result_organization AS SELECT substr(r.target, 4) AS id, substr(r.source, 4) AS organization FROM ${openaire_db_name}.relation r WHERE r.reltype='resultOrganization'; +CREATE TABLE ${stats_db_name}.result_organization AS SELECT substr(r.target, 4) AS id, substr(r.source, 4) AS organization FROM ${openaire_db_name}.relation r WHERE r.reltype='resultOrganization' and r.datainfo.deletedbyinference=false; DROP TABLE IF EXISTS ${stats_db_name}.result_projects; CREATE TABLE ${stats_db_name}.result_projects AS select pr.result AS id, pr.id AS project, datediff(p.enddate, p.startdate) AS daysfromend FROM ${stats_db_name}.result r JOIN ${stats_db_name}.project_results pr ON r.id=pr.result JOIN ${stats_db_name}.project_tmp p ON p.id=pr.id; diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step8.sql b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step8.sql index 4e13b3dd8..197047c8b 100644 --- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step8.sql +++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step8.sql @@ -47,7 +47,7 @@ DROP TABLE IF EXISTS ${stats_db_name}.datasource_oids; CREATE TABLE ${stats_db_name}.datasource_oids AS SELECT substr(d.id, 4) AS id, oids.ids AS oid FROM ${openaire_db_name}.datasource d LATERAL VIEW explode(d.originalid) oids AS ids; DROP TABLE IF EXISTS ${stats_db_name}.datasource_organizations; -CREATE TABLE ${stats_db_name}.datasource_organizations AS SELECT substr(r.target, 4) AS id, substr(r.source, 4) AS organization FROM ${openaire_db_name}.relation r WHERE r.reltype='datasourceOrganization'; +CREATE TABLE ${stats_db_name}.datasource_organizations AS SELECT substr(r.target, 4) AS id, substr(r.source, 4) AS organization FROM ${openaire_db_name}.relation r WHERE r.reltype='datasourceOrganization' and r.datainfo.deletedbyinference=false; -- datasource sources: -- where the datasource info have been collected from. From 77a3a6d82e1e9e36d30809d075094d4bf9865298 Mon Sep 17 00:00:00 2001 From: antleb Date: Fri, 4 Dec 2020 13:04:25 +0200 Subject: [PATCH 055/445] added the new parameter (stats_tool_api_url) in the workflow parameters --- .../eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml index 451461669..dcd034166 100644 --- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml @@ -17,6 +17,10 @@ stats_db_shadow_name the name of the shadow schema + + stats_tool_api_url + The url of the API of the stats tool. Is used to trigger the cache update. + hive_metastore_uris hive server metastore URIs From aead9efd245c40b9ef1b6c2975dcf364e5528e0d Mon Sep 17 00:00:00 2001 From: antleb Date: Fri, 4 Dec 2020 13:07:18 +0200 Subject: [PATCH 056/445] added the new parameter (stats_tool_api_url) in the workflow parameters --- .../eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml index ae2318238..d744f18da 100644 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml @@ -8,6 +8,10 @@ stats_db_production_name the name of the production schema + + stats_tool_api_url + The url of the API of the stats tool. Is used to trigger the cache promote. + hive_metastore_uris hive server metastore URIs From fcd7689b502b4c4486ab522f8a6d3a8c15879080 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 9 Dec 2020 13:10:16 +0100 Subject: [PATCH 057/445] promote actions: shouldGroupById parameter marked as optional (default is true) --- ...promote_action_payload_for_graph_table_input_parameters.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/promote/promote_action_payload_for_graph_table_input_parameters.json b/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/promote/promote_action_payload_for_graph_table_input_parameters.json index f5eae8a30..00c9404ef 100644 --- a/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/promote/promote_action_payload_for_graph_table_input_parameters.json +++ b/dhp-workflows/dhp-actionmanager/src/main/resources/eu/dnetlib/dhp/actionmanager/promote/promote_action_payload_for_graph_table_input_parameters.json @@ -45,6 +45,6 @@ "paramName": "sgid", "paramLongName": "shouldGroupById", "paramDescription": "indicates whether the promotion operation should group objects in the graph by id or not", - "paramRequired": true + "paramRequired": false } ] \ No newline at end of file From 3c5ce1dadaed252293a8687acba3210500200ed0 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 9 Dec 2020 17:07:20 +0100 Subject: [PATCH 058/445] code formatting --- .../dhp/schema/oaf/CleaningFunctions.java | 6 ++--- .../schema/oaf/utils/IdentifierFactory.java | 13 +++++----- .../PromoteActionPayloadForGraphTableJob.java | 24 +++++++++---------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index 8ce4285d6..da4ed63a9 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -261,9 +261,9 @@ public class CleaningFunctions { */ private static boolean filterPid(StructuredProperty pid) { String value = Optional - .ofNullable(pid.getValue()) - .map(s -> StringUtils.replaceAll(s, "\\s", "")) - .orElse(""); + .ofNullable(pid.getValue()) + .map(s -> StringUtils.replaceAll(s, "\\s", "")) + .orElse(""); if (StringUtils.isBlank(value)) { return false; } diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 9978194ac..fd90ee126 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -1,12 +1,6 @@ package eu.dnetlib.dhp.schema.oaf.utils; -import eu.dnetlib.dhp.schema.oaf.CleaningFunctions; -import eu.dnetlib.dhp.schema.oaf.OafEntity; -import eu.dnetlib.dhp.schema.oaf.StructuredProperty; -import eu.dnetlib.dhp.utils.DHPUtils; -import org.apache.commons.lang3.StringUtils; - import java.io.Serializable; import java.util.List; import java.util.Map; @@ -14,6 +8,13 @@ import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; + +import eu.dnetlib.dhp.schema.oaf.CleaningFunctions; +import eu.dnetlib.dhp.schema.oaf.OafEntity; +import eu.dnetlib.dhp.schema.oaf.StructuredProperty; +import eu.dnetlib.dhp.utils.DHPUtils; + /** * Factory class for OpenAIRE identifiers in the Graph */ diff --git a/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadForGraphTableJob.java b/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadForGraphTableJob.java index be775f358..bab4377bd 100644 --- a/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadForGraphTableJob.java +++ b/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadForGraphTableJob.java @@ -69,9 +69,9 @@ public class PromoteActionPayloadForGraphTableJob { logger.info("strategy: {}", strategy); Boolean shouldGroupById = Optional - .ofNullable(parser.get("shouldGroupById")) - .map(Boolean::valueOf) - .orElse(true); + .ofNullable(parser.get("shouldGroupById")) + .map(Boolean::valueOf) + .orElse(true); logger.info("shouldGroupById: {}", shouldGroupById); Class rowClazz = (Class) Class.forName(graphTableClassName); @@ -116,13 +116,13 @@ public class PromoteActionPayloadForGraphTableJob { } private static void promoteActionPayloadForGraphTable( - SparkSession spark, - String inputGraphTablePath, - String inputActionPayloadPath, - String outputGraphTablePath, - MergeAndGet.Strategy strategy, - Class rowClazz, - Class actionPayloadClazz, Boolean shouldGroupById) { + SparkSession spark, + String inputGraphTablePath, + String inputActionPayloadPath, + String outputGraphTablePath, + MergeAndGet.Strategy strategy, + Class rowClazz, + Class actionPayloadClazz, Boolean shouldGroupById) { Dataset rowDS = readGraphTable(spark, inputGraphTablePath, rowClazz); Dataset actionPayloadDS = readActionPayload(spark, inputActionPayloadPath, actionPayloadClazz); @@ -208,8 +208,8 @@ public class PromoteActionPayloadForGraphTableJob { if (shouldGroupById) { return PromoteActionPayloadFunctions - .groupGraphTableByIdAndMerge( - joinedAndMerged, rowIdFn, mergeRowsAndGetFn, zeroFn, isNotZeroFn, rowClazz); + .groupGraphTableByIdAndMerge( + joinedAndMerged, rowIdFn, mergeRowsAndGetFn, zeroFn, isNotZeroFn, rowClazz); } else { return joinedAndMerged; } From 1eaad89a3c0f25756ea4e9b0d8fb1265f71f133e Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 10 Dec 2020 15:56:11 +0100 Subject: [PATCH 059/445] do not fail on uknown properties when grouping entities by ID --- .../java/eu/dnetlib/dhp/oa/dedup/GroupEntitiesSparkJob.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/GroupEntitiesSparkJob.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/GroupEntitiesSparkJob.java index ec991cddc..3f01db0fa 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/GroupEntitiesSparkJob.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/GroupEntitiesSparkJob.java @@ -10,6 +10,7 @@ import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; +import com.fasterxml.jackson.databind.DeserializationFeature; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.spark.SparkConf; @@ -42,9 +43,8 @@ public class GroupEntitiesSparkJob { private final static String ID_JPATH = "$.id"; - private final static String SOURCE_JPATH = "$.source"; - - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static ObjectMapper OBJECT_MAPPER = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); public static void main(String[] args) throws Exception { From d9532446eb7583be4b01eaa0ec2f12f822ec59d9 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 10 Dec 2020 16:14:16 +0100 Subject: [PATCH 060/445] imported more diffs from master branch; code formatting --- .../dhp/schema/common/ModelConstants.java | 4 ++ .../dhp/actionmanager/bipfinder/BipScore.java | 4 +- .../bipfinder/SparkAtomicActionScoreJob.java | 1 - .../dhp/oa/dedup/GroupEntitiesSparkJob.java | 4 +- .../dnetlib/doiboost/orcid/ORCIDToOAF.scala | 37 +++++++------ .../orcid/SparkConvertORCIDToOAF.scala | 32 +++++------ .../dhp/oa/provision/XmlIndexingJob.java | 55 +++++++++++++------ 7 files changed, 83 insertions(+), 54 deletions(-) diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java index 26149c62c..e72a0d69c 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java @@ -7,6 +7,10 @@ import eu.dnetlib.dhp.schema.oaf.Qualifier; public class ModelConstants { + public static final String ORCID = "orcid"; + public static final String ORCID_PENDING = "orcid_pending"; + public static final String ORCID_CLASSNAME = "Open Researcher and Contributor ID"; + public static String CROSSREF_ID = "10|openaire____::081b82f96300b6a6e3d282bad31cb6e2"; public static String DATACITE_ID = "10|openaire____::9e3be59865b2c1c335d32dae2fe7b254"; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/bipfinder/BipScore.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/bipfinder/BipScore.java index da9a4bd0c..247546694 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/bipfinder/BipScore.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/bipfinder/BipScore.java @@ -9,8 +9,8 @@ import java.util.List; */ public class BipScore implements Serializable { - private String id; //doi - private List scoreList; //unit as given in the inputfile + private String id; // doi + private List scoreList; // unit as given in the inputfile public String getId() { return id; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/bipfinder/SparkAtomicActionScoreJob.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/bipfinder/SparkAtomicActionScoreJob.java index 23764f38e..50bda898c 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/bipfinder/SparkAtomicActionScoreJob.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/bipfinder/SparkAtomicActionScoreJob.java @@ -144,7 +144,6 @@ public class SparkAtomicActionScoreJob implements Serializable { } - private static List getMeasure(BipScore value) { return value .getScoreList() diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/GroupEntitiesSparkJob.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/GroupEntitiesSparkJob.java index 3f01db0fa..a3726d60a 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/GroupEntitiesSparkJob.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/GroupEntitiesSparkJob.java @@ -10,7 +10,6 @@ import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; -import com.fasterxml.jackson.databind.DeserializationFeature; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.spark.SparkConf; @@ -22,6 +21,7 @@ import org.apache.spark.sql.expressions.Aggregator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.DocumentContext; @@ -44,7 +44,7 @@ public class GroupEntitiesSparkJob { private final static String ID_JPATH = "$.id"; private static ObjectMapper OBJECT_MAPPER = new ObjectMapper() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); public static void main(String[] args) throws Exception { diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala index 1e01ed16d..1d669f4b5 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala @@ -1,7 +1,7 @@ package eu.dnetlib.doiboost.orcid -import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory -import eu.dnetlib.dhp.schema.oaf.{Author, Publication} +import eu.dnetlib.dhp.schema.oaf.{Author, DataInfo, Publication} +import eu.dnetlib.dhp.schema.orcid.OrcidDOI import eu.dnetlib.doiboost.DoiBoostMappingUtil import eu.dnetlib.doiboost.DoiBoostMappingUtil.{ORCID, PID_TYPES, createSP, generateDataInfo, generateIdentifier} import org.apache.commons.lang.StringUtils @@ -44,23 +44,19 @@ object ORCIDToOAF { } - def convertTOOAF(input:ORCIDElement) :Publication = { - val doi = input.doi + def convertTOOAF(input:OrcidDOI) :Publication = { + val doi = input.getDoi val pub:Publication = new Publication - pub.setPid(List(createSP(doi, "doi", PID_TYPES)).asJava) + pub.setPid(List(createSP(doi.toLowerCase, "doi", PID_TYPES)).asJava) pub.setDataInfo(generateDataInfo()) - - //IMPORTANT - //The old method pub.setId(IdentifierFactory.createIdentifier(pub)) - //will be replaced using IdentifierFactory pub.setId(generateIdentifier(pub, doi.toLowerCase)) - pub.setId(IdentifierFactory.createIdentifier(pub)) - - try{ - pub.setAuthor(input.authors.map(a=> { - generateAuthor(a.name, a.surname, a.creditName, a.oid) - }).asJava) + + val l:List[Author]= input.getAuthors.asScala.map(a=> { + generateAuthor(a.getName, a.getSurname, a.getCreditName, a.getOid) + })(collection.breakOut) + + pub.setAuthor(l.asJava) pub.setCollectedfrom(List(DoiBoostMappingUtil.createORIDCollectedFrom()).asJava) pub.setDataInfo(DoiBoostMappingUtil.generateDataInfo()) pub @@ -71,6 +67,13 @@ object ORCIDToOAF { } } + def generateOricPIDDatainfo():DataInfo = { + val di =DoiBoostMappingUtil.generateDataInfo("0.91") + di.getProvenanceaction.setClassid("sysimport:crosswalk:entityregistry") + di.getProvenanceaction.setClassname("Harvested") + di + } + def generateAuthor(given: String, family: String, fullName:String, orcid: String): Author = { val a = new Author a.setName(given) @@ -80,10 +83,10 @@ object ORCIDToOAF { else a.setFullname(s"$given $family") if (StringUtils.isNotBlank(orcid)) - a.setPid(List(createSP(orcid, ORCID, PID_TYPES)).asJava) + a.setPid(List(createSP(orcid, ORCID, PID_TYPES, generateOricPIDDatainfo())).asJava) a } -} +} \ No newline at end of file diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkConvertORCIDToOAF.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkConvertORCIDToOAF.scala index f1c7c58b4..f1d718d0d 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkConvertORCIDToOAF.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkConvertORCIDToOAF.scala @@ -45,24 +45,24 @@ object SparkConvertORCIDToOAF { Encoders.kryo(classOf[Publication]) } -def run(spark:SparkSession,sourcePath:String, targetPath:String):Unit = { - implicit val mapEncoderPubs: Encoder[Publication] = Encoders.kryo[Publication] - implicit val mapOrcid: Encoder[OrcidDOI] = Encoders.kryo[OrcidDOI] - implicit val tupleForJoinEncoder: Encoder[(String, Publication)] = Encoders.tuple(Encoders.STRING, mapEncoderPubs) + def run(spark:SparkSession,sourcePath:String, targetPath:String):Unit = { + implicit val mapEncoderPubs: Encoder[Publication] = Encoders.kryo[Publication] + implicit val mapOrcid: Encoder[OrcidDOI] = Encoders.kryo[OrcidDOI] + implicit val tupleForJoinEncoder: Encoder[(String, Publication)] = Encoders.tuple(Encoders.STRING, mapEncoderPubs) - val mapper = new ObjectMapper() - mapper.getDeserializationConfig.withFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + val mapper = new ObjectMapper() + mapper.getDeserializationConfig.withFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - val dataset:Dataset[OrcidDOI] = spark.createDataset(spark.sparkContext.textFile(sourcePath).map(s => mapper.readValue(s,classOf[OrcidDOI]))) + val dataset:Dataset[OrcidDOI] = spark.createDataset(spark.sparkContext.textFile(sourcePath).map(s => mapper.readValue(s,classOf[OrcidDOI]))) - logger.info("Converting ORCID to OAF") - dataset.map(o => ORCIDToOAF.convertTOOAF(o)).filter(p=>p!=null) - .map(d => (d.getId, d)) - .groupByKey(_._1)(Encoders.STRING) - .agg(getPublicationAggregator().toColumn) - .map(p => p._2) - .write.mode(SaveMode.Overwrite).save(targetPath) -} + logger.info("Converting ORCID to OAF") + dataset.map(o => ORCIDToOAF.convertTOOAF(o)).filter(p=>p!=null) + .map(d => (d.getId, d)) + .groupByKey(_._1)(Encoders.STRING) + .agg(getPublicationAggregator().toColumn) + .map(p => p._2) + .write.mode(SaveMode.Overwrite).save(targetPath) + } def main(args: Array[String]): Unit = { @@ -85,4 +85,4 @@ def run(spark:SparkSession,sourcePath:String, targetPath:String):Unit = { } -} +} \ No newline at end of file diff --git a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/XmlIndexingJob.java b/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/XmlIndexingJob.java index 48538c059..9ff387c8c 100644 --- a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/XmlIndexingJob.java +++ b/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/XmlIndexingJob.java @@ -10,6 +10,7 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.Optional; +import javax.swing.text.html.Option; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.stream.StreamResult; @@ -42,6 +43,10 @@ public class XmlIndexingJob { private static final Logger log = LoggerFactory.getLogger(XmlIndexingJob.class); + public enum OutputFormat { + SOLR, HDFS + } + private static final Integer DEFAULT_BATCH_SIZE = 1000; protected static final String DATE_FORMAT = "yyyy-MM-dd'T'hh:mm:ss'Z'"; @@ -52,6 +57,8 @@ public class XmlIndexingJob { private int batchSize; + private OutputFormat outputFormat; + private String outputPath; private SparkSession spark; @@ -80,14 +87,22 @@ public class XmlIndexingJob { final String outputPath = Optional .ofNullable(parser.get("outputPath")) + .map(StringUtils::trim) .orElse(null); log.info("outputPath: {}", outputPath); - final Integer batchSize = parser.getObjectMap().containsKey("batchSize") - ? Integer.valueOf(parser.get("batchSize")) - : DEFAULT_BATCH_SIZE; + final Integer batchSize = Optional + .ofNullable(parser.get("batchSize")) + .map(Integer::valueOf) + .orElse(DEFAULT_BATCH_SIZE); log.info("batchSize: {}", batchSize); + final OutputFormat outputFormat = Optional + .ofNullable(parser.get("outputFormat")) + .map(OutputFormat::valueOf) + .orElse(OutputFormat.SOLR); + log.info("outputFormat: {}", outputFormat); + final SparkConf conf = new SparkConf(); conf.registerKryoClasses(new Class[] { SerializableSolrInputDocument.class @@ -100,15 +115,18 @@ public class XmlIndexingJob { final String isLookupUrl = parser.get("isLookupUrl"); log.info("isLookupUrl: {}", isLookupUrl); final ISLookupClient isLookup = new ISLookupClient(ISLookupClientFactory.getLookUpService(isLookupUrl)); - new XmlIndexingJob(spark, inputPath, format, batchSize, outputPath).run(isLookup); + new XmlIndexingJob(spark, inputPath, format, batchSize, outputFormat, outputPath).run(isLookup); }); } - public XmlIndexingJob(SparkSession spark, String inputPath, String format, Integer batchSize, String outputPath) { + public XmlIndexingJob(SparkSession spark, String inputPath, String format, Integer batchSize, + OutputFormat outputFormat, + String outputPath) { this.spark = spark; this.inputPath = inputPath; this.format = format; this.batchSize = batchSize; + this.outputFormat = outputFormat; this.outputPath = outputPath; } @@ -137,17 +155,22 @@ public class XmlIndexingJob { .map(s -> toIndexRecord(SaxonTransformerFactory.newInstance(indexRecordXslt), s)) .map(s -> new StreamingInputDocumentFactory(version, dsId).parseDocument(s)); - if (StringUtils.isNotBlank(outputPath)) { - spark - .createDataset( - docs.map(s -> new SerializableSolrInputDocument(s)).rdd(), - Encoders.kryo(SerializableSolrInputDocument.class)) - .write() - .mode(SaveMode.Overwrite) - .parquet(outputPath); - } else { - final String collection = ProvisionConstants.getCollectionName(format); - SolrSupport.indexDocs(zkHost, collection, batchSize, docs.rdd()); + switch (outputFormat) { + case SOLR: + final String collection = ProvisionConstants.getCollectionName(format); + SolrSupport.indexDocs(zkHost, collection, batchSize, docs.rdd()); + break; + case HDFS: + spark + .createDataset( + docs.map(s -> new SerializableSolrInputDocument(s)).rdd(), + Encoders.kryo(SerializableSolrInputDocument.class)) + .write() + .mode(SaveMode.Overwrite) + .parquet(outputPath); + break; + default: + throw new IllegalArgumentException("invalid outputFormat: " + outputFormat); } } From 1e1aab83e30085b8763d9452f47f77fac4ed2c27 Mon Sep 17 00:00:00 2001 From: miconis Date: Mon, 21 Dec 2020 11:58:21 +0100 Subject: [PATCH 061/445] implementation of the raw wf for openorgs: still not complete, some functionalities are missing --- .../dhp/oa/dedup/SparkCopySimRels.java | 110 ++++++ .../dhp/oa/dedup/SparkRemoveDiffRels.java | 233 +++++++++++++ .../openorgs/oozie_app/config-default.xml | 18 + .../oa/dedup/openorgs/oozie_app/workflow.xml | 324 ++++++++++++++++++ 4 files changed, 685 insertions(+) create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopySimRels.java create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkRemoveDiffRels.java create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/config-default.xml create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopySimRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopySimRels.java new file mode 100644 index 000000000..4409aaee7 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopySimRels.java @@ -0,0 +1,110 @@ +package eu.dnetlib.dhp.oa.dedup; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.oaf.DataInfo; +import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import eu.dnetlib.pace.config.DedupConfig; +import org.apache.commons.io.IOUtils; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaRDD; +import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.SparkSession; +import org.dom4j.DocumentException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Optional; + +//copy simrels (verified) from relation to the workdir in order to make them available for the deduplication +public class SparkCopySimRels extends AbstractSparkAction{ + private static final Logger log = LoggerFactory.getLogger(SparkCopySimRels.class); + + public SparkCopySimRels(ArgumentApplicationParser parser, SparkSession spark) { + super(parser, spark); + } + + public static void main(String[] args) throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCreateSimRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/createSimRels_parameters.json"))); + parser.parseArgument(args); + + SparkConf conf = new SparkConf(); + new SparkCreateSimRels(parser, getSparkSession(conf)) + .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); + } + + @Override + public void run(ISLookUpService isLookUpService) + throws DocumentException, IOException, ISLookUpException { + + // read oozie parameters + final String graphBasePath = parser.get("graphBasePath"); + final String isLookUpUrl = parser.get("isLookUpUrl"); + final String actionSetId = parser.get("actionSetId"); + final String workingPath = parser.get("workingPath"); + final int numPartitions = Optional + .ofNullable(parser.get("numPartitions")) + .map(Integer::valueOf) + .orElse(NUM_PARTITIONS); + + log.info("numPartitions: '{}'", numPartitions); + log.info("graphBasePath: '{}'", graphBasePath); + log.info("isLookUpUrl: '{}'", isLookUpUrl); + log.info("actionSetId: '{}'", actionSetId); + log.info("workingPath: '{}'", workingPath); + + // for each dedup configuration + for (DedupConfig dedupConf : getConfigurations(isLookUpService, actionSetId)) { + + final String entity = dedupConf.getWf().getEntityType(); + final String subEntity = dedupConf.getWf().getSubEntityValue(); + log.info("Copying simrels for: '{}'", subEntity); + + final String outputPath = DedupUtility.createSimRelPath(workingPath, actionSetId, subEntity); + removeOutputDir(spark, outputPath); + + final String relationPath = DedupUtility.createEntityPath(graphBasePath, "relation"); + + JavaRDD simRels = spark.read().textFile(relationPath).map(patchRelFn(), Encoders.bean(Relation.class)).toJavaRDD().filter(r -> filterRels(r, entity)); + + simRels.saveAsTextFile(outputPath); + } + } + + private static MapFunction patchRelFn() { + return value -> { + final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); + if (rel.getDataInfo() == null) { + rel.setDataInfo(new DataInfo()); + } + return rel; + }; + } + + private boolean filterRels(Relation rel, String entityType) { + + switch(entityType) { + case "result": + if (rel.getRelClass().equals("isSimilarTo") && rel.getRelType().equals("resultResult") && rel.getSubRelType().equals("dedup")) + return true; + break; + case "organization": + if (rel.getRelClass().equals("isSimilarTo") && rel.getRelType().equals("organizationOrganization") && rel.getSubRelType().equals("dedup")) + return true; + break; + default: + return false; + } + return false; + } +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkRemoveDiffRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkRemoveDiffRels.java new file mode 100644 index 000000000..030f3b783 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkRemoveDiffRels.java @@ -0,0 +1,233 @@ +package eu.dnetlib.dhp.oa.dedup; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.oa.dedup.graph.ConnectedComponent; +import eu.dnetlib.dhp.oa.dedup.graph.GraphProcessor; +import eu.dnetlib.dhp.oa.dedup.model.Block; +import eu.dnetlib.dhp.schema.oaf.DataInfo; +import eu.dnetlib.dhp.schema.oaf.Qualifier; +import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import eu.dnetlib.pace.config.DedupConfig; +import eu.dnetlib.pace.model.MapDocument; +import eu.dnetlib.pace.util.MapDocumentUtil; +import org.apache.commons.io.IOUtils; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaPairRDD; +import org.apache.spark.api.java.JavaRDD; +import org.apache.spark.api.java.JavaSparkContext; +import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.api.java.function.PairFunction; +import org.apache.spark.graphx.Edge; +import org.apache.spark.rdd.RDD; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.SaveMode; +import org.apache.spark.sql.SparkSession; +import org.dom4j.DocumentException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import scala.Tuple2; + +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import static eu.dnetlib.dhp.oa.dedup.SparkCreateMergeRels.DNET_PROVENANCE_ACTIONS; +import static eu.dnetlib.dhp.oa.dedup.SparkCreateMergeRels.PROVENANCE_ACTION_CLASS; +import static eu.dnetlib.dhp.oa.dedup.SparkCreateMergeRels.hash; + +public class SparkRemoveDiffRels extends AbstractSparkAction { + + private static final Logger log = LoggerFactory.getLogger(SparkRemoveDiffRels.class); + + public SparkRemoveDiffRels(ArgumentApplicationParser parser, SparkSession spark) { + super(parser, spark); + } + + public static void main(String[] args) throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCreateSimRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/createSimRels_parameters.json"))); + parser.parseArgument(args); + + SparkConf conf = new SparkConf(); + new SparkCreateSimRels(parser, getSparkSession(conf)) + .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); + } + + @Override + public void run(ISLookUpService isLookUpService) + throws DocumentException, IOException, ISLookUpException { + + // read oozie parameters + final String graphBasePath = parser.get("graphBasePath"); + final String isLookUpUrl = parser.get("isLookUpUrl"); + final String actionSetId = parser.get("actionSetId"); + final String workingPath = parser.get("workingPath"); + final int numPartitions = Optional + .ofNullable(parser.get("numPartitions")) + .map(Integer::valueOf) + .orElse(NUM_PARTITIONS); + + log.info("numPartitions: '{}'", numPartitions); + log.info("graphBasePath: '{}'", graphBasePath); + log.info("isLookUpUrl: '{}'", isLookUpUrl); + log.info("actionSetId: '{}'", actionSetId); + log.info("workingPath: '{}'", workingPath); + + // for each dedup configuration + for (DedupConfig dedupConf : getConfigurations(isLookUpService, actionSetId)) { + + final String entity = dedupConf.getWf().getEntityType(); + final String subEntity = dedupConf.getWf().getSubEntityValue(); + log.info("Removing diffrels for: '{}'", subEntity); + + final String mergeRelsPath = DedupUtility.createMergeRelPath(workingPath, actionSetId, subEntity); + + final String relationPath = DedupUtility.createEntityPath(graphBasePath, subEntity); + + final int maxIterations = dedupConf.getWf().getMaxIterations(); + log.info("Max iterations {}", maxIterations); + + JavaRDD mergeRelsRDD = spark + .read() + .load(mergeRelsPath) + .as(Encoders.bean(Relation.class)) + .where("relClass == 'merges'") + .toJavaRDD(); + + JavaRDD, String>> diffRelsRDD = spark + .read() + .textFile(relationPath) + .map(patchRelFn(), Encoders.bean(Relation.class)) + .toJavaRDD().filter(r -> filterRels(r, entity)) + .map(rel -> { + if (rel.getSource().compareTo(rel.getTarget()) < 0) + return new Tuple2<>(new Tuple2<>(rel.getSource(), rel.getTarget()), "diffRel"); + else + return new Tuple2<>(new Tuple2<>(rel.getTarget(), rel.getSource()), "diffRel"); + }); + + JavaRDD, String>> flatMergeRels = mergeRelsRDD + .mapToPair(rel -> new Tuple2<>(rel.getSource(), rel.getTarget())) + .groupByKey() + .flatMap(g -> { + List, String>> rels = new ArrayList<>(); + + List ids = StreamSupport + .stream(g._2().spliterator(), false) + .collect(Collectors.toList()); + + for (int i = 0; i < ids.size(); i++){ + for (int j = i+1; j < ids.size(); j++){ + if (ids.get(i).compareTo(ids.get(j)) < 0) + rels.add(new Tuple2<>(new Tuple2<>(ids.get(i), ids.get(j)), g._1())); + else + rels.add(new Tuple2<>(new Tuple2<>(ids.get(j), ids.get(i)), g._1())); + } + } + return rels.iterator(); + + }); + + JavaRDD purgedMergeRels = flatMergeRels.union(diffRelsRDD) + .mapToPair(rel -> new Tuple2<>(rel._1(), Arrays.asList(rel._2()))) + .reduceByKey((a, b) -> { + List list = new ArrayList(); + list.addAll(a); + list.addAll(b); + return list; + }) + .filter(rel -> rel._2().size() == 1) + .mapToPair(rel -> new Tuple2<>(rel._2().get(0), rel._1())) + .flatMap(rel -> { + List> rels = new ArrayList<>(); + String source = rel._1(); + rels.add(new Tuple2<>(source, rel._2()._1())); + rels.add(new Tuple2<>(source, rel._2()._2())); + return rels.iterator(); + }) + .distinct() + .flatMap(rel -> tupleToMergeRel(rel, dedupConf)); + + spark + .createDataset(purgedMergeRels.rdd(), Encoders.bean(Relation.class)) + .write() + .mode(SaveMode.Overwrite).parquet(mergeRelsPath); + } + } + + private static MapFunction patchRelFn() { + return value -> { + final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); + if (rel.getDataInfo() == null) { + rel.setDataInfo(new DataInfo()); + } + return rel; + }; + } + + private boolean filterRels(Relation rel, String entityType) { + + switch(entityType) { + case "result": + if (rel.getRelClass().equals("isDifferentFrom") && rel.getRelType().equals("resultResult") && rel.getSubRelType().equals("dedup")) + return true; + break; + case "organization": + if (rel.getRelClass().equals("isDifferentFrom") && rel.getRelType().equals("organizationOrganization") && rel.getSubRelType().equals("dedup")) + return true; + break; + default: + return false; + } + return false; + } + + public Iterator tupleToMergeRel(Tuple2 rel, DedupConfig dedupConf) { + + List rels = new ArrayList<>(); + + rels.add(rel(rel._1(), rel._2(), "merges", dedupConf)); + rels.add(rel(rel._2(), rel._1(), "isMergedIn", dedupConf)); + + return rels.iterator(); + } + + private Relation rel(String source, String target, String relClass, DedupConfig dedupConf) { + + String entityType = dedupConf.getWf().getEntityType(); + + Relation r = new Relation(); + r.setSource(source); + r.setTarget(target); + r.setRelClass(relClass); + r.setRelType(entityType + entityType.substring(0, 1).toUpperCase() + entityType.substring(1)); + r.setSubRelType("dedup"); + + DataInfo info = new DataInfo(); + info.setDeletedbyinference(false); + info.setInferred(true); + info.setInvisible(false); + info.setInferenceprovenance(dedupConf.getWf().getConfigurationId()); + Qualifier provenanceAction = new Qualifier(); + provenanceAction.setClassid(PROVENANCE_ACTION_CLASS); + provenanceAction.setClassname(PROVENANCE_ACTION_CLASS); + provenanceAction.setSchemeid(DNET_PROVENANCE_ACTIONS); + provenanceAction.setSchemename(DNET_PROVENANCE_ACTIONS); + info.setProvenanceaction(provenanceAction); + + // TODO calculate the trust value based on the similarity score of the elements in the CC + // info.setTrust(); + + r.setDataInfo(info); + return r; + } +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/config-default.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/config-default.xml new file mode 100644 index 000000000..2e0ed9aee --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/config-default.xml @@ -0,0 +1,18 @@ + + + jobTracker + yarnRM + + + nameNode + hdfs://nameservice1 + + + oozie.use.system.libpath + true + + + oozie.action.sharelib.for.spark + spark2 + + \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml new file mode 100644 index 000000000..092a5c30e --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml @@ -0,0 +1,324 @@ + + + + graphBasePath + the raw graph base path + + + isLookUpUrl + the address of the lookUp service + + + actionSetId + id of the actionSet + + + workingPath + path for the working directory + + + dedupGraphPath + path for the output graph + + + cutConnectedComponent + max number of elements in a connected component + + + sparkDriverMemory + memory for driver process + + + sparkExecutorMemory + memory for individual executor + + + sparkExecutorCores + number of cores used by single executor + + + oozieActionShareLibForSpark2 + oozie action sharelib for spark 2.* + + + spark2ExtraListeners + com.cloudera.spark.lineage.NavigatorAppListener + spark 2.* extra listeners classname + + + spark2SqlQueryExecutionListeners + com.cloudera.spark.lineage.NavigatorQueryListener + spark 2.* sql query execution listeners classname + + + spark2YarnHistoryServerAddress + spark 2.* yarn history server address + + + spark2EventLogDir + spark 2.* event log dir location + + + + + ${jobTracker} + ${nameNode} + + + mapreduce.job.queuename + ${queueName} + + + oozie.launcher.mapred.job.queue.name + ${oozieLauncherQueueName} + + + oozie.action.sharelib.for.spark + ${oozieActionShareLibForSpark2} + + + + + + + + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] + + + + + + + + + + + + + + yarn + cluster + Copy Similarity Relations + eu.dnetlib.dhp.oa.dedup.SparkCopySimRels + dhp-dedup-openaire-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=3840 + + --graphBasePath${graphBasePath} + --workingPath${workingPath} + --isLookUpUrl${isLookUpUrl} + --actionSetId${actionSetId} + --numPartitions8000 + + + + + + + + + + + + yarn + cluster + Create Dedup Record + eu.dnetlib.dhp.oa.dedup.SparkCreateDedupRecord + dhp-dedup-openaire-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=3840 + + --graphBasePath${graphBasePath} + --workingPath${workingPath} + --isLookUpUrl${isLookUpUrl} + --actionSetId${actionSetId} + + + + + + + + yarn + cluster + Update Entity + eu.dnetlib.dhp.oa.dedup.SparkUpdateEntity + dhp-dedup-openaire-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=3840 + + --graphBasePath${graphBasePath} + --workingPath${workingPath} + --dedupGraphPath${dedupGraphPath} + + + + + + + + yarn + cluster + Create Similarity Relations + eu.dnetlib.dhp.oa.dedup.SparkCreateSimRels + dhp-dedup-openaire-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=3840 + + --graphBasePath${graphBasePath} + --isLookUpUrl${isLookUpUrl} + --actionSetId${actionSetId} + --workingPath${workingPath} + --numPartitions8000 + + + + + + + + yarn + cluster + Create Merge Relations + eu.dnetlib.dhp.oa.dedup.SparkCreateMergeRels + dhp-dedup-openaire-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=3840 + + --graphBasePath${graphBasePath} + --workingPath${workingPath} + --isLookUpUrl${isLookUpUrl} + --actionSetId${actionSetId} + --cutConnectedComponent${cutConnectedComponent} + + + + + + + + yarn + cluster + Create Merge Relations + eu.dnetlib.dhp.oa.dedup.SparkRemoveDiffRels + dhp-dedup-openaire-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=3840 + + --graphBasePath${graphBasePath} + --isLookUpUrl${isLookUpUrl} + --actionSetId${actionSetId} + --workingPath${workingPath} + --numPartitions8000 + + + + + + + + yarn + cluster + Prepare Organization Relations + eu.dnetlib.dhp.oa.dedup.SparkPrepareOrgRels + dhp-dedup-openaire-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=3840 + + --graphBasePath${graphBasePath} + --workingPath${workingPath} + --isLookUpUrl${isLookUpUrl} + --actionSetId${actionSetId} + --dbUrl${dbUrl} + --dbTable${dbTable} + --dbUser${dbUser} + --dbPwd${dbPwd} + --numConnections20 + + + + + + + + yarn + cluster + Prepare New Organizations + eu.dnetlib.dhp.oa.dedup.SparkPrepareNewOrgs + dhp-dedup-openaire-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=3840 + + --graphBasePath${graphBasePath} + --workingPath${workingPath} + --isLookUpUrl${isLookUpUrl} + --actionSetId${actionSetId} + --apiUrl${apiUrl} + --dbUrl${dbUrl} + --dbTable${dbTable} + --dbUser${dbUser} + --dbPwd${dbPwd} + --numConnections20 + + + + + + + \ No newline at end of file From 8fea29177ccd9d98940151862cea853167bdc045 Mon Sep 17 00:00:00 2001 From: miconis Date: Mon, 18 Jan 2021 16:48:08 +0100 Subject: [PATCH 062/445] refactoring, minor changes and implementation of the wf for openorgs with integration of organization phases into the scan wf --- .../dhp/oa/dedup/SparkCopyOpenorgs.java | 100 ++++++++++++++++++ ...arkCopySimRels.java => SparkCopyRels.java} | 53 +++++----- .../dhp/oa/dedup/copyOpenorgs_parameters.json | 26 +++++ .../dhp/oa/dedup/copyRels_parameters.json | 32 ++++++ .../oa/dedup/openorgs/oozie_app/workflow.xml | 65 +----------- .../dhp/oa/dedup/scan/oozie_app/workflow.xml | 55 ++++++++++ 6 files changed, 247 insertions(+), 84 deletions(-) create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java rename dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/{SparkCopySimRels.java => SparkCopyRels.java} (70%) create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/copyOpenorgs_parameters.json create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/copyRels_parameters.json diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java new file mode 100644 index 000000000..12ae4e73a --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java @@ -0,0 +1,100 @@ +package eu.dnetlib.dhp.oa.dedup; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.common.EntityType; +import eu.dnetlib.dhp.schema.common.ModelSupport; +import eu.dnetlib.dhp.schema.oaf.OafEntity; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import org.apache.commons.io.IOUtils; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.SaveMode; +import org.apache.spark.sql.SparkSession; +import org.dom4j.DocumentException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Optional; + +public class SparkCopyOpenorgs extends AbstractSparkAction{ + private static final Logger log = LoggerFactory.getLogger(SparkCopyRels.class); + + public SparkCopyOpenorgs(ArgumentApplicationParser parser, SparkSession spark) { + super(parser, spark); + } + + public static void main(String[] args) throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCreateSimRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/copyOpenorgs_parameters.json"))); + parser.parseArgument(args); + + SparkConf conf = new SparkConf(); + new SparkCopyOpenorgs(parser, getSparkSession(conf)) + .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); + } + + @Override + public void run(ISLookUpService isLookUpService) + throws DocumentException, IOException, ISLookUpException { + + // read oozie parameters + final String graphBasePath = parser.get("graphBasePath"); + final String actionSetId = parser.get("actionSetId"); + final String workingPath = parser.get("workingPath"); + final int numPartitions = Optional + .ofNullable(parser.get("numPartitions")) + .map(Integer::valueOf) + .orElse(NUM_PARTITIONS); + + log.info("numPartitions: '{}'", numPartitions); + log.info("graphBasePath: '{}'", graphBasePath); + log.info("actionSetId: '{}'", actionSetId); + log.info("workingPath: '{}'", workingPath); + + String subEntity = "organization"; + log.info("Copying openorgs to the working dir"); + + final String outputPath = DedupUtility.createDedupRecordPath(workingPath, actionSetId, subEntity); + removeOutputDir(spark, outputPath); + + final String entityPath = DedupUtility.createEntityPath(graphBasePath, subEntity); + + final Class clazz = ModelSupport.entityTypes.get(EntityType.valueOf(subEntity)); + + filterEntities(spark, entityPath, clazz) + .write() + .mode(SaveMode.Overwrite) + .option("compression", "gzip") + .json(outputPath); + + } + + public static Dataset filterEntities( + final SparkSession spark, + final String entitiesInputPath, + final Class clazz) { + + // + Dataset entities = spark + .read() + .textFile(entitiesInputPath) + .map( + (MapFunction) it -> { + T entity = OBJECT_MAPPER.readValue(it, clazz); + return entity; + }, + Encoders.kryo(clazz)); + + return entities.filter(entities.col("id").contains("openorgs____")); + } + +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopySimRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRels.java similarity index 70% rename from dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopySimRels.java rename to dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRels.java index 4409aaee7..802085ab9 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopySimRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRels.java @@ -6,12 +6,10 @@ import eu.dnetlib.dhp.schema.oaf.Relation; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import eu.dnetlib.pace.config.DedupConfig; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.function.MapFunction; -import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoders; import org.apache.spark.sql.SparkSession; import org.dom4j.DocumentException; @@ -22,10 +20,10 @@ import java.io.IOException; import java.util.Optional; //copy simrels (verified) from relation to the workdir in order to make them available for the deduplication -public class SparkCopySimRels extends AbstractSparkAction{ - private static final Logger log = LoggerFactory.getLogger(SparkCopySimRels.class); +public class SparkCopyRels extends AbstractSparkAction{ + private static final Logger log = LoggerFactory.getLogger(SparkCopyRels.class); - public SparkCopySimRels(ArgumentApplicationParser parser, SparkSession spark) { + public SparkCopyRels(ArgumentApplicationParser parser, SparkSession spark) { super(parser, spark); } @@ -33,13 +31,13 @@ public class SparkCopySimRels extends AbstractSparkAction{ ArgumentApplicationParser parser = new ArgumentApplicationParser( IOUtils .toString( - SparkCreateSimRels.class + SparkCopyRels.class .getResourceAsStream( - "/eu/dnetlib/dhp/oa/dedup/createSimRels_parameters.json"))); + "/eu/dnetlib/dhp/oa/dedup/copyRels_parameters.json"))); parser.parseArgument(args); SparkConf conf = new SparkConf(); - new SparkCreateSimRels(parser, getSparkSession(conf)) + new SparkCopyRels(parser, getSparkSession(conf)) .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); } @@ -49,9 +47,10 @@ public class SparkCopySimRels extends AbstractSparkAction{ // read oozie parameters final String graphBasePath = parser.get("graphBasePath"); - final String isLookUpUrl = parser.get("isLookUpUrl"); final String actionSetId = parser.get("actionSetId"); final String workingPath = parser.get("workingPath"); + final String destination = parser.get("destination"); + final String entity = parser.get("entityType"); final int numPartitions = Optional .ofNullable(parser.get("numPartitions")) .map(Integer::valueOf) @@ -59,26 +58,32 @@ public class SparkCopySimRels extends AbstractSparkAction{ log.info("numPartitions: '{}'", numPartitions); log.info("graphBasePath: '{}'", graphBasePath); - log.info("isLookUpUrl: '{}'", isLookUpUrl); log.info("actionSetId: '{}'", actionSetId); log.info("workingPath: '{}'", workingPath); + log.info("entity: '{}'", entity); - // for each dedup configuration - for (DedupConfig dedupConf : getConfigurations(isLookUpService, actionSetId)) { + log.info("Copying " + destination + " for: '{}'", entity); - final String entity = dedupConf.getWf().getEntityType(); - final String subEntity = dedupConf.getWf().getSubEntityValue(); - log.info("Copying simrels for: '{}'", subEntity); - - final String outputPath = DedupUtility.createSimRelPath(workingPath, actionSetId, subEntity); - removeOutputDir(spark, outputPath); - - final String relationPath = DedupUtility.createEntityPath(graphBasePath, "relation"); - - JavaRDD simRels = spark.read().textFile(relationPath).map(patchRelFn(), Encoders.bean(Relation.class)).toJavaRDD().filter(r -> filterRels(r, entity)); - - simRels.saveAsTextFile(outputPath); + final String outputPath; + if (destination.contains("mergerel")) { + outputPath = DedupUtility.createMergeRelPath(workingPath, actionSetId, entity); } + else { + outputPath = DedupUtility.createSimRelPath(workingPath, actionSetId, entity); + } + + removeOutputDir(spark, outputPath); + + final String relationPath = DedupUtility.createEntityPath(graphBasePath, "relation"); + + JavaRDD simRels = + spark.read() + .textFile(relationPath) + .map(patchRelFn(), Encoders.bean(Relation.class)) + .toJavaRDD() + .filter(r -> filterRels(r, entity)); + + simRels.saveAsTextFile(outputPath); } private static MapFunction patchRelFn() { diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/copyOpenorgs_parameters.json b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/copyOpenorgs_parameters.json new file mode 100644 index 000000000..e45efca01 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/copyOpenorgs_parameters.json @@ -0,0 +1,26 @@ +[ + { + "paramName": "asi", + "paramLongName": "actionSetId", + "paramDescription": "action set identifier (name of the orchestrator)", + "paramRequired": true + }, + { + "paramName": "i", + "paramLongName": "graphBasePath", + "paramDescription": "the base path of the raw graph", + "paramRequired": true + }, + { + "paramName": "w", + "paramLongName": "workingPath", + "paramDescription": "path of the working directory", + "paramRequired": true + }, + { + "paramName": "np", + "paramLongName": "numPartitions", + "paramDescription": "number of partitions for the similarity relations intermediate phases", + "paramRequired": false + } +] \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/copyRels_parameters.json b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/copyRels_parameters.json new file mode 100644 index 000000000..715b0e74e --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/copyRels_parameters.json @@ -0,0 +1,32 @@ +[ + { + "paramName": "asi", + "paramLongName": "actionSetId", + "paramDescription": "action set identifier (name of the orchestrator)", + "paramRequired": true + }, + { + "paramName": "i", + "paramLongName": "graphBasePath", + "paramDescription": "the base path of the raw graph", + "paramRequired": true + }, + { + "paramName": "w", + "paramLongName": "workingPath", + "paramDescription": "path of the working directory", + "paramRequired": true + }, + { + "paramName": "e", + "paramLongName": "entityType", + "paramDescription": "type of the entity for the merge relations", + "paramRequired": true + }, + { + "paramName": "np", + "paramLongName": "numPartitions", + "paramDescription": "number of partitions for the similarity relations intermediate phases", + "paramRequired": false + } +] \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml index 092a5c30e..2f961face 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml @@ -93,13 +93,12 @@ - - + yarn cluster - Copy Similarity Relations - eu.dnetlib.dhp.oa.dedup.SparkCopySimRels + Copy Merge Relations + eu.dnetlib.dhp.oa.dedup.SparkCopyRels dhp-dedup-openaire-${projectVersion}.jar --executor-memory=${sparkExecutorMemory} @@ -113,65 +112,11 @@ --graphBasePath${graphBasePath} --workingPath${workingPath} - --isLookUpUrl${isLookUpUrl} --actionSetId${actionSetId} + --entityTypeorganization + --destinationsimrel --numPartitions8000 - - - - - - - - - - - yarn - cluster - Create Dedup Record - eu.dnetlib.dhp.oa.dedup.SparkCreateDedupRecord - dhp-dedup-openaire-${projectVersion}.jar - - --executor-memory=${sparkExecutorMemory} - --executor-cores=${sparkExecutorCores} - --driver-memory=${sparkDriverMemory} - --conf spark.extraListeners=${spark2ExtraListeners} - --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} - --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} - --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} - --conf spark.sql.shuffle.partitions=3840 - - --graphBasePath${graphBasePath} - --workingPath${workingPath} - --isLookUpUrl${isLookUpUrl} - --actionSetId${actionSetId} - - - - - - - - yarn - cluster - Update Entity - eu.dnetlib.dhp.oa.dedup.SparkUpdateEntity - dhp-dedup-openaire-${projectVersion}.jar - - --executor-memory=${sparkExecutorMemory} - --executor-cores=${sparkExecutorCores} - --driver-memory=${sparkDriverMemory} - --conf spark.extraListeners=${spark2ExtraListeners} - --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} - --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} - --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} - --conf spark.sql.shuffle.partitions=3840 - - --graphBasePath${graphBasePath} - --workingPath${workingPath} - --dedupGraphPath${dedupGraphPath} - diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml index c42ce1263..d22f05ca8 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml @@ -169,6 +169,61 @@ --isLookUpUrl${isLookUpUrl} --actionSetId${actionSetId} + + + + + + + + yarn + cluster + Copy Merge Relations + eu.dnetlib.dhp.oa.dedup.SparkCopyRels + dhp-dedup-openaire-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=3840 + + --graphBasePath${graphBasePath} + --workingPath${workingPath} + --actionSetId${actionSetId} + --entityTypeorganization + --destinationmergerel + --numPartitions8000 + + + + + + + + yarn + cluster + Copy Entities + eu.dnetlib.dhp.oa.dedup.SparkCopyOpenorgs + dhp-dedup-openaire-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=3840 + + --graphBasePath${graphBasePath} + --workingPath${workingPath} + --isLookUpUrl${isLookUpUrl} + --actionSetId${actionSetId} + From c7e2d5a59addfcbf71c4c5e4319823bd34136cd6 Mon Sep 17 00:00:00 2001 From: miconis Date: Mon, 25 Jan 2021 12:40:45 +0100 Subject: [PATCH 063/445] minor changes --- .../eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml index 2f961face..a6b313cad 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml @@ -85,6 +85,9 @@ Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] + + + From cda210a2ca993a20507108eb94970d89e05da3af Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Mon, 25 Jan 2021 14:17:42 +0100 Subject: [PATCH 064/445] changed documentation since it didn't reflect the current status --- dhp-workflows/dhp-aggregation/README.md | 31 +++++++------------------ 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/README.md b/dhp-workflows/dhp-aggregation/README.md index 02583b443..e46fdeb16 100644 --- a/dhp-workflows/dhp-aggregation/README.md +++ b/dhp-workflows/dhp-aggregation/README.md @@ -2,28 +2,15 @@ Description of the Module -------------------------- This module defines a **collector worker application** that runs on Hadoop. -It is responsible for harvesting metadata using different plugins. +It is responsible for harvesting metadata using different collector plugins and transformation into the common metadata model. -The collector worker uses a message queue to inform the progress -of the harvesting action (using a message queue for sending **ONGOING** messages) furthermore, -It gives, at the end of the job, some information about the status -of the collection i.e Number of records collected(using a message queue for sending **REPORT** messages). - -To work the collection worker need some parameter like: - -* **hdfsPath**: the path where storing the sequential file -* **apidescriptor**: the JSON encoding of the API Descriptor -* **namenode**: the Name Node URI -* **userHDFS**: the user wich create the hdfs seq file -* **rabbitUser**: the user to connect with RabbitMq for messaging -* **rabbitPassWord**: the password to connect with RabbitMq for messaging -* **rabbitHost**: the host of the RabbitMq server -* **rabbitOngoingQueue**: the name of the ongoing queue -* **rabbitReportQueue**: the name of the report queue -* **workflowId**: the identifier of the dnet Workflow - -##Plugins +# Collector Plugins * OAI Plugin -## Usage -TODO \ No newline at end of file +# Transformation Plugins +TODO + + +# Usage +TODO + From ffb092b8d3edf5e3f451c1499de073b6ca341efc Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Mon, 25 Jan 2021 15:05:37 +0100 Subject: [PATCH 065/445] removed duplicate code HttpConnector.java --- .../CollectorPluginErrorLogList.java | 20 -- .../CollectorServiceException.java | 20 -- .../project/httpconnector/HttpConnector.java | 240 ------------------ .../actionmanager/project/utils/ReadCSV.java | 2 +- .../project/utils/ReadExcel.java | 3 +- .../GenerateNativeStoreSparkJob.java | 198 ++++++++------- .../project/EXCELParserTest.java | 7 +- .../httpconnector/HttpConnectorTest.java | 6 +- .../eu/dnetlib/dhp/transform/ext_simple.xsl | 4 +- 9 files changed, 113 insertions(+), 387 deletions(-) delete mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/CollectorPluginErrorLogList.java delete mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/CollectorServiceException.java delete mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnector.java diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/CollectorPluginErrorLogList.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/CollectorPluginErrorLogList.java deleted file mode 100644 index 9d3f88265..000000000 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/CollectorPluginErrorLogList.java +++ /dev/null @@ -1,20 +0,0 @@ - -package eu.dnetlib.dhp.actionmanager.project.httpconnector; - -import java.util.LinkedList; - -public class CollectorPluginErrorLogList extends LinkedList { - - private static final long serialVersionUID = -6925786561303289704L; - - @Override - public String toString() { - String log = new String(); - int index = 0; - for (String errorMessage : this) { - log += String.format("Retry #%s: %s / ", index++, errorMessage); - } - return log; - } - -} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/CollectorServiceException.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/CollectorServiceException.java deleted file mode 100644 index 9167d97b4..000000000 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/CollectorServiceException.java +++ /dev/null @@ -1,20 +0,0 @@ - -package eu.dnetlib.dhp.actionmanager.project.httpconnector; - -public class CollectorServiceException extends Exception { - - private static final long serialVersionUID = 7523999812098059764L; - - public CollectorServiceException(String string) { - super(string); - } - - public CollectorServiceException(String string, Throwable exception) { - super(string, exception); - } - - public CollectorServiceException(Throwable exception) { - super(exception); - } - -} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnector.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnector.java deleted file mode 100644 index e20518b55..000000000 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnector.java +++ /dev/null @@ -1,240 +0,0 @@ - -package eu.dnetlib.dhp.actionmanager.project.httpconnector; - -import java.io.IOException; -import java.io.InputStream; -import java.net.*; -import java.security.GeneralSecurityException; -import java.security.cert.X509Certificate; -import java.util.List; -import java.util.Map; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.math.NumberUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * @author jochen, michele, andrea - */ -public class HttpConnector { - - private static final Log log = LogFactory.getLog(HttpConnector.class); - - private int maxNumberOfRetry = 6; - private int defaultDelay = 120; // seconds - private int readTimeOut = 120; // seconds - - private String responseType = null; - - private String userAgent = "Mozilla/5.0 (compatible; OAI; +http://www.openaire.eu)"; - - public HttpConnector() { - CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL)); - } - - /** - * Given the URL returns the content via HTTP GET - * - * @param requestUrl the URL - * @return the content of the downloaded resource - * @throws CollectorServiceException when retrying more than maxNumberOfRetry times - */ - public String getInputSource(final String requestUrl) throws CollectorServiceException { - return attemptDownlaodAsString(requestUrl, 1, new CollectorPluginErrorLogList()); - } - - /** - * Given the URL returns the content as a stream via HTTP GET - * - * @param requestUrl the URL - * @return the content of the downloaded resource as InputStream - * @throws CollectorServiceException when retrying more than maxNumberOfRetry times - */ - public InputStream getInputSourceAsStream(final String requestUrl) throws CollectorServiceException { - return attemptDownload(requestUrl, 1, new CollectorPluginErrorLogList()); - } - - private String attemptDownlaodAsString(final String requestUrl, final int retryNumber, - final CollectorPluginErrorLogList errorList) - throws CollectorServiceException { - try { - InputStream s = attemptDownload(requestUrl, 1, new CollectorPluginErrorLogList()); - try { - return IOUtils.toString(s); - } catch (IOException e) { - log.error("error while retrieving from http-connection occured: " + requestUrl, e); - Thread.sleep(defaultDelay * 1000); - errorList.add(e.getMessage()); - return attemptDownlaodAsString(requestUrl, retryNumber + 1, errorList); - } finally { - IOUtils.closeQuietly(s); - } - } catch (InterruptedException e) { - throw new CollectorServiceException(e); - } - } - - private InputStream attemptDownload(final String requestUrl, final int retryNumber, - final CollectorPluginErrorLogList errorList) - throws CollectorServiceException { - - if (retryNumber > maxNumberOfRetry) { - throw new CollectorServiceException("Max number of retries exceeded. Cause: \n " + errorList); - } - - log.debug("Downloading " + requestUrl + " - try: " + retryNumber); - try { - InputStream input = null; - - try { - final HttpURLConnection urlConn = (HttpURLConnection) new URL(requestUrl).openConnection(); - urlConn.setInstanceFollowRedirects(false); - urlConn.setReadTimeout(readTimeOut * 1000); - urlConn.addRequestProperty("User-Agent", userAgent); - - if (log.isDebugEnabled()) { - logHeaderFields(urlConn); - } - - int retryAfter = obtainRetryAfter(urlConn.getHeaderFields()); - if (retryAfter > 0 && urlConn.getResponseCode() == HttpURLConnection.HTTP_UNAVAILABLE) { - log.warn("waiting and repeating request after " + retryAfter + " sec."); - Thread.sleep(retryAfter * 1000); - errorList.add("503 Service Unavailable"); - urlConn.disconnect(); - return attemptDownload(requestUrl, retryNumber + 1, errorList); - } else if ((urlConn.getResponseCode() == HttpURLConnection.HTTP_MOVED_PERM) - || (urlConn.getResponseCode() == HttpURLConnection.HTTP_MOVED_TEMP)) { - final String newUrl = obtainNewLocation(urlConn.getHeaderFields()); - log.debug("The requested url has been moved to " + newUrl); - errorList - .add( - String - .format( - "%s %s. Moved to: %s", urlConn.getResponseCode(), urlConn.getResponseMessage(), - newUrl)); - urlConn.disconnect(); - return attemptDownload(newUrl, retryNumber + 1, errorList); - } else if (urlConn.getResponseCode() != HttpURLConnection.HTTP_OK) { - log - .error( - String - .format("HTTP error: %s %s", urlConn.getResponseCode(), urlConn.getResponseMessage())); - Thread.sleep(defaultDelay * 1000); - errorList.add(String.format("%s %s", urlConn.getResponseCode(), urlConn.getResponseMessage())); - urlConn.disconnect(); - return attemptDownload(requestUrl, retryNumber + 1, errorList); - } else { - input = urlConn.getInputStream(); - responseType = urlConn.getContentType(); - return input; - } - } catch (IOException e) { - log.error("error while retrieving from http-connection occured: " + requestUrl, e); - Thread.sleep(defaultDelay * 1000); - errorList.add(e.getMessage()); - return attemptDownload(requestUrl, retryNumber + 1, errorList); - } - } catch (InterruptedException e) { - throw new CollectorServiceException(e); - } - } - - private void logHeaderFields(final HttpURLConnection urlConn) throws IOException { - log.debug("StatusCode: " + urlConn.getResponseMessage()); - - for (Map.Entry> e : urlConn.getHeaderFields().entrySet()) { - if (e.getKey() != null) { - for (String v : e.getValue()) { - log.debug(" key: " + e.getKey() + " - value: " + v); - } - } - } - } - - private int obtainRetryAfter(final Map> headerMap) { - for (String key : headerMap.keySet()) { - if ((key != null) && key.toLowerCase().equals("retry-after") && (headerMap.get(key).size() > 0) - && NumberUtils.isCreatable(headerMap.get(key).get(0))) { - return Integer - .parseInt(headerMap.get(key).get(0)) + 10; - } - } - return -1; - } - - private String obtainNewLocation(final Map> headerMap) throws CollectorServiceException { - for (String key : headerMap.keySet()) { - if ((key != null) && key.toLowerCase().equals("location") && (headerMap.get(key).size() > 0)) { - return headerMap.get(key).get(0); - } - } - throw new CollectorServiceException("The requested url has been MOVED, but 'location' param is MISSING"); - } - - /** - * register for https scheme; this is a workaround and not intended for the use in trusted environments - */ - public void initTrustManager() { - final X509TrustManager tm = new X509TrustManager() { - - @Override - public void checkClientTrusted(final X509Certificate[] xcs, final String string) { - } - - @Override - public void checkServerTrusted(final X509Certificate[] xcs, final String string) { - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return null; - } - }; - try { - final SSLContext ctx = SSLContext.getInstance("TLS"); - ctx.init(null, new TrustManager[] { - tm - }, null); - HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory()); - } catch (GeneralSecurityException e) { - log.fatal(e); - throw new IllegalStateException(e); - } - } - - public int getMaxNumberOfRetry() { - return maxNumberOfRetry; - } - - public void setMaxNumberOfRetry(final int maxNumberOfRetry) { - this.maxNumberOfRetry = maxNumberOfRetry; - } - - public int getDefaultDelay() { - return defaultDelay; - } - - public void setDefaultDelay(final int defaultDelay) { - this.defaultDelay = defaultDelay; - } - - public int getReadTimeOut() { - return readTimeOut; - } - - public void setReadTimeOut(final int readTimeOut) { - this.readTimeOut = readTimeOut; - } - - public String getResponseType() { - return responseType; - } - -} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java index 9dac34a15..dc6f46771 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java @@ -17,7 +17,7 @@ import org.apache.hadoop.fs.Path; import com.fasterxml.jackson.databind.ObjectMapper; -import eu.dnetlib.dhp.actionmanager.project.httpconnector.HttpConnector; +import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; import eu.dnetlib.dhp.application.ArgumentApplicationParser; /** diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java index 23b58f2a0..e665bc704 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java @@ -4,6 +4,7 @@ package eu.dnetlib.dhp.actionmanager.project.utils; import java.io.*; import java.nio.charset.StandardCharsets; +import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -14,7 +15,7 @@ import org.apache.hadoop.fs.Path; import com.fasterxml.jackson.databind.ObjectMapper; -import eu.dnetlib.dhp.actionmanager.project.httpconnector.HttpConnector; + import eu.dnetlib.dhp.application.ArgumentApplicationParser; /** diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java index 861ae5201..c0bd4c940 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java @@ -43,6 +43,106 @@ public class GenerateNativeStoreSparkJob { private static final Logger log = LoggerFactory.getLogger(GenerateNativeStoreSparkJob.class); + public static void main(String[] args) throws Exception { + + final ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + GenerateNativeStoreSparkJob.class + .getResourceAsStream( + "/eu/dnetlib/dhp/collection/collection_input_parameters.json"))); + parser.parseArgument(args); + final ObjectMapper jsonMapper = new ObjectMapper(); + final Provenance provenance = jsonMapper.readValue(parser.get("provenance"), Provenance.class); + final long dateOfCollection = new Long(parser.get("dateOfCollection")); + + Boolean isSparkSessionManaged = Optional + .ofNullable(parser.get("isSparkSessionManaged")) + .map(Boolean::valueOf) + .orElse(Boolean.TRUE); + log.info("isSparkSessionManaged: {}", isSparkSessionManaged); + + final Map ongoingMap = new HashMap<>(); + final Map reportMap = new HashMap<>(); + + final boolean test = parser.get("isTest") == null ? false : Boolean.valueOf(parser.get("isTest")); + + SparkConf conf = new SparkConf(); + runWithSparkSession( + conf, + isSparkSessionManaged, + spark -> { + final JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); + + final JavaPairRDD inputRDD = sc + .sequenceFile(parser.get("input"), IntWritable.class, Text.class); + + final LongAccumulator totalItems = sc.sc().longAccumulator("TotalItems"); + final LongAccumulator invalidRecords = sc.sc().longAccumulator("InvalidRecords"); + + final MessageManager manager = new MessageManager( + parser.get("rabbitHost"), + parser.get("rabbitUser"), + parser.get("rabbitPassword"), + false, + false, + null); + + final JavaRDD mappeRDD = inputRDD + .map( + item -> parseRecord( + item._2().toString(), + parser.get("xpath"), + parser.get("encoding"), + provenance, + dateOfCollection, + totalItems, + invalidRecords)) + .filter(Objects::nonNull) + .distinct(); + + ongoingMap.put("ongoing", "0"); + if (!test) { + manager + .sendMessage( + new Message( + parser.get("workflowId"), "DataFrameCreation", MessageType.ONGOING, ongoingMap), + parser.get("rabbitOngoingQueue"), + true, + false); + } + + final Encoder encoder = Encoders.bean(MetadataRecord.class); + final Dataset mdstore = spark.createDataset(mappeRDD.rdd(), encoder); + final LongAccumulator mdStoreRecords = sc.sc().longAccumulator("MDStoreRecords"); + mdStoreRecords.add(mdstore.count()); + ongoingMap.put("ongoing", "" + totalItems.value()); + if (!test) { + manager + .sendMessage( + new Message( + parser.get("workflowId"), "DataFrameCreation", MessageType.ONGOING, ongoingMap), + parser.get("rabbitOngoingQueue"), + true, + false); + } + mdstore.write().format("parquet").save(parser.get("output")); + reportMap.put("inputItem", "" + totalItems.value()); + reportMap.put("invalidRecords", "" + invalidRecords.value()); + reportMap.put("mdStoreSize", "" + mdStoreRecords.value()); + if (!test) { + manager + .sendMessage( + new Message(parser.get("workflowId"), "Collection", MessageType.REPORT, reportMap), + parser.get("rabbitReportQueue"), + true, + false); + manager.close(); + } + }); + + } + public static MetadataRecord parseRecord( final String input, final String xpath, @@ -73,103 +173,5 @@ public class GenerateNativeStoreSparkJob { } } - public static void main(String[] args) throws Exception { - final ArgumentApplicationParser parser = new ArgumentApplicationParser( - IOUtils - .toString( - GenerateNativeStoreSparkJob.class - .getResourceAsStream( - "/eu/dnetlib/dhp/collection/collection_input_parameters.json"))); - parser.parseArgument(args); - final ObjectMapper jsonMapper = new ObjectMapper(); - final Provenance provenance = jsonMapper.readValue(parser.get("provenance"), Provenance.class); - final long dateOfCollection = new Long(parser.get("dateOfCollection")); - - Boolean isSparkSessionManaged = Optional - .ofNullable(parser.get("isSparkSessionManaged")) - .map(Boolean::valueOf) - .orElse(Boolean.TRUE); - log.info("isSparkSessionManaged: {}", isSparkSessionManaged); - - final Map ongoingMap = new HashMap<>(); - final Map reportMap = new HashMap<>(); - - final boolean test = parser.get("isTest") == null ? false : Boolean.valueOf(parser.get("isTest")); - - SparkConf conf = new SparkConf(); - runWithSparkSession( - conf, - isSparkSessionManaged, - spark -> { - final JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); - - final JavaPairRDD inputRDD = sc - .sequenceFile(parser.get("input"), IntWritable.class, Text.class); - - final LongAccumulator totalItems = sc.sc().longAccumulator("TotalItems"); - final LongAccumulator invalidRecords = sc.sc().longAccumulator("InvalidRecords"); - - final MessageManager manager = new MessageManager( - parser.get("rabbitHost"), - parser.get("rabbitUser"), - parser.get("rabbitPassword"), - false, - false, - null); - - final JavaRDD mappeRDD = inputRDD - .map( - item -> parseRecord( - item._2().toString(), - parser.get("xpath"), - parser.get("encoding"), - provenance, - dateOfCollection, - totalItems, - invalidRecords)) - .filter(Objects::nonNull) - .distinct(); - - ongoingMap.put("ongoing", "0"); - if (!test) { - manager - .sendMessage( - new Message( - parser.get("workflowId"), "DataFrameCreation", MessageType.ONGOING, ongoingMap), - parser.get("rabbitOngoingQueue"), - true, - false); - } - - final Encoder encoder = Encoders.bean(MetadataRecord.class); - final Dataset mdstore = spark.createDataset(mappeRDD.rdd(), encoder); - final LongAccumulator mdStoreRecords = sc.sc().longAccumulator("MDStoreRecords"); - mdStoreRecords.add(mdstore.count()); - ongoingMap.put("ongoing", "" + totalItems.value()); - if (!test) { - manager - .sendMessage( - new Message( - parser.get("workflowId"), "DataFrameCreation", MessageType.ONGOING, ongoingMap), - parser.get("rabbitOngoingQueue"), - true, - false); - } - mdstore.write().format("parquet").save(parser.get("output")); - reportMap.put("inputItem", "" + totalItems.value()); - reportMap.put("invalidRecords", "" + invalidRecords.value()); - reportMap.put("mdStoreSize", "" + mdStoreRecords.value()); - if (!test) { - manager - .sendMessage( - new Message(parser.get("workflowId"), "Collection", MessageType.REPORT, reportMap), - parser.get("rabbitReportQueue"), - true, - false); - manager.close(); - } - }); - - } } diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java index 59b536cd5..c1142ad9c 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java @@ -6,14 +6,15 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import eu.dnetlib.dhp.collection.worker.DnetCollectorException; +import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import eu.dnetlib.dhp.actionmanager.project.httpconnector.CollectorServiceException; -import eu.dnetlib.dhp.actionmanager.project.httpconnector.HttpConnector; + import eu.dnetlib.dhp.actionmanager.project.utils.EXCELParser; @Disabled @@ -30,7 +31,7 @@ public class EXCELParserTest { } @Test - public void test1() throws CollectorServiceException, IOException, InvalidFormatException, ClassNotFoundException, + public void test1() throws DnetCollectorException, IOException, InvalidFormatException, ClassNotFoundException, IllegalAccessException, InstantiationException { EXCELParser excelParser = new EXCELParser(); diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java index 90b3919ed..3b9d1c3ab 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java @@ -1,6 +1,8 @@ package eu.dnetlib.dhp.actionmanager.project.httpconnector; +import eu.dnetlib.dhp.collection.worker.DnetCollectorException; +import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; @@ -29,12 +31,12 @@ public class HttpConnectorTest { @Test - public void testGetInputSource() throws CollectorServiceException { + public void testGetInputSource() throws DnetCollectorException { System.out.println(connector.getInputSource(URL)); } @Test - public void testGoodServers() throws CollectorServiceException { + public void testGoodServers() throws DnetCollectorException { System.out.println(connector.getInputSource(URL_GOODSNI_SERVER)); } diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl index cef50aa95..f22db961b 100644 --- a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl @@ -1,7 +1,7 @@ @@ -9,7 +9,7 @@ - + From a54848a59c2b4971569f2036ef2851f87c578701 Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Mon, 25 Jan 2021 15:43:04 +0100 Subject: [PATCH 066/445] Moved Vocabulary stuff to common module --- .../java/eu/dnetlib/dhp/common/vocabulary}/Vocabulary.java | 2 +- .../eu/dnetlib/dhp/common/vocabulary}/VocabularyGroup.java | 2 +- .../eu/dnetlib/dhp/common/vocabulary}/VocabularyTerm.java | 2 +- .../eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java | 2 +- .../java/eu/dnetlib/dhp/oa/graph/clean/CleaningRuleMap.java | 2 +- .../dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java | 2 +- .../dhp/oa/graph/raw/GenerateEntitiesApplication.java | 5 +---- .../dhp/oa/graph/raw/MigrateDbEntitiesApplication.java | 2 +- .../java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java | 2 +- .../java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java | 2 +- .../eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java | 2 +- .../dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java | 2 +- .../test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java | 2 +- .../dhp/oa/graph/raw/MigrateDbEntitiesApplicationTest.java | 2 +- 14 files changed, 14 insertions(+), 17 deletions(-) rename {dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common => dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary}/Vocabulary.java (98%) rename {dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common => dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary}/VocabularyGroup.java (99%) rename {dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common => dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary}/VocabularyTerm.java (88%) diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/Vocabulary.java b/dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary/Vocabulary.java similarity index 98% rename from dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/Vocabulary.java rename to dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary/Vocabulary.java index bfc4fd6f1..1e333e93f 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/Vocabulary.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary/Vocabulary.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.oa.graph.raw.common; +package eu.dnetlib.dhp.common.vocabulary; import java.io.Serializable; import java.util.HashMap; diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/VocabularyGroup.java b/dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary/VocabularyGroup.java similarity index 99% rename from dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/VocabularyGroup.java rename to dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary/VocabularyGroup.java index 32452bdc5..fac55189b 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/VocabularyGroup.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary/VocabularyGroup.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.oa.graph.raw.common; +package eu.dnetlib.dhp.common.vocabulary; import java.io.Serializable; import java.util.*; diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/VocabularyTerm.java b/dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary/VocabularyTerm.java similarity index 88% rename from dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/VocabularyTerm.java rename to dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary/VocabularyTerm.java index 1aa1b8253..52eb7ca23 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/VocabularyTerm.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary/VocabularyTerm.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.oa.graph.raw.common; +package eu.dnetlib.dhp.common.vocabulary; import java.io.Serializable; diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java index 8231dd77e..31fe116e3 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.common.HdfsSupport; -import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.schema.oaf.Oaf; import eu.dnetlib.dhp.schema.oaf.OafEntity; import eu.dnetlib.dhp.utils.ISLookupClientFactory; diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleaningRuleMap.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleaningRuleMap.java index d2d4e118f..e0cb354d4 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleaningRuleMap.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleaningRuleMap.java @@ -7,7 +7,7 @@ import java.util.HashMap; import org.apache.commons.lang3.StringUtils; import eu.dnetlib.dhp.common.FunctionalInterfaceSupport.SerializableConsumer; -import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.Country; import eu.dnetlib.dhp.schema.oaf.Qualifier; diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java index cccf15398..079984a81 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java @@ -38,7 +38,7 @@ import org.dom4j.DocumentFactory; import org.dom4j.DocumentHelper; import org.dom4j.Node; -import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.schema.common.LicenseComparator; import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.Author; diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java index cfd190670..a2db4a506 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplication.java @@ -3,7 +3,6 @@ package eu.dnetlib.dhp.oa.graph.raw; import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; -import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -12,8 +11,6 @@ import java.util.stream.Collectors; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.hadoop.fs.FileSystem; -import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.Text; import org.apache.hadoop.io.compress.GzipCodec; import org.apache.spark.SparkConf; @@ -27,7 +24,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.common.HdfsSupport; -import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.dhp.utils.ISLookupClientFactory; diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java index 3adbd244c..db1a2ef57 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java @@ -57,7 +57,7 @@ import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.common.DbClient; import eu.dnetlib.dhp.oa.graph.raw.common.AbstractMigrationApplication; import eu.dnetlib.dhp.oa.graph.raw.common.VerifyNsPrefixPredicate; -import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.schema.oaf.Context; import eu.dnetlib.dhp.schema.oaf.DataInfo; import eu.dnetlib.dhp.schema.oaf.Dataset; diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java index e62bc0790..4a8b24cf2 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java @@ -18,7 +18,7 @@ import org.dom4j.Node; import com.google.common.collect.Lists; import eu.dnetlib.dhp.common.PacePerson; -import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.schema.oaf.Author; import eu.dnetlib.dhp.schema.oaf.DataInfo; import eu.dnetlib.dhp.schema.oaf.Field; diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java index 6d2e28ba8..bc47e778f 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java @@ -19,7 +19,7 @@ import org.dom4j.Node; import com.google.common.collect.Lists; import eu.dnetlib.dhp.common.PacePerson; -import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.schema.oaf.Author; import eu.dnetlib.dhp.schema.oaf.DataInfo; import eu.dnetlib.dhp.schema.oaf.Field; diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java index cb34b0cb3..9e161da6e 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java @@ -18,7 +18,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import com.fasterxml.jackson.databind.ObjectMapper; -import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.schema.oaf.Publication; import eu.dnetlib.dhp.schema.oaf.Qualifier; import eu.dnetlib.dhp.schema.oaf.Result; diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java index 705f1dddb..8293faac4 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java @@ -16,7 +16,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import eu.dnetlib.dhp.oa.graph.clean.CleaningFunctionTest; -import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java index 2a62e25b2..5c8e4e4c6 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java @@ -22,7 +22,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.oa.graph.clean.CleaningFunctionTest; -import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.Author; import eu.dnetlib.dhp.schema.oaf.Dataset; diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplicationTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplicationTest.java index 0d1ec1ad1..b38da4569 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplicationTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplicationTest.java @@ -27,7 +27,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.schema.oaf.*; @ExtendWith(MockitoExtension.class) From 184e7b385675120faa14d120221ee7221a454ac0 Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Wed, 27 Jan 2021 15:43:08 +0100 Subject: [PATCH 067/445] Implemented new Transformation using spark --- .../common/AggregationCounter.java | 45 + .../DnetTransformationException.java | 28 + .../dhp/transformation/TransformFunction.java | 74 - .../transformation/TransformSparkJobNode.java | 86 +- .../transformation/TransformationFactory.java | 62 + .../{functions => xslt}/Cleaner.java | 21 +- .../xslt/XSLTTransformationFunction.java | 66 + .../transformation_input_parameters.json | 38 +- .../transformation/TransformationJobTest.java | 163 +-- .../eu/dnetlib/dhp/transform/ext_simple.xsl | 9 +- .../eu/dnetlib/dhp/transform/input.xml | 99 +- .../eu/dnetlib/dhp/transform/synonyms.txt | 1234 +++++++++++++++++ .../eu/dnetlib/dhp/transform/terms.txt | 1080 +++++++++++++++ 13 files changed, 2704 insertions(+), 301 deletions(-) create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationCounter.java create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/DnetTransformationException.java delete mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformFunction.java create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java rename dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/{functions => xslt}/Cleaner.java (61%) create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/synonyms.txt create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/terms.txt diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationCounter.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationCounter.java new file mode 100644 index 000000000..1ac2cb54b --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationCounter.java @@ -0,0 +1,45 @@ +package eu.dnetlib.dhp.aggregation.common; + +import org.apache.spark.util.LongAccumulator; + +import java.io.Serializable; + + +public class AggregationCounter implements Serializable { + private LongAccumulator totalItems; + private LongAccumulator errorItems; + private LongAccumulator processedItems; + + public AggregationCounter() { + } + + public AggregationCounter(LongAccumulator totalItems, LongAccumulator errorItems, LongAccumulator processedItems) { + this.totalItems = totalItems; + this.errorItems = errorItems; + this.processedItems = processedItems; + } + + public LongAccumulator getTotalItems() { + return totalItems; + } + + public void setTotalItems(LongAccumulator totalItems) { + this.totalItems = totalItems; + } + + public LongAccumulator getErrorItems() { + return errorItems; + } + + public void setErrorItems(LongAccumulator errorItems) { + this.errorItems = errorItems; + } + + public LongAccumulator getProcessedItems() { + return processedItems; + } + + public void setProcessedItems(LongAccumulator processedItems) { + this.processedItems = processedItems; + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/DnetTransformationException.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/DnetTransformationException.java new file mode 100644 index 000000000..2c932e40b --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/DnetTransformationException.java @@ -0,0 +1,28 @@ +package eu.dnetlib.dhp.transformation; + +public class DnetTransformationException extends Exception { + + public DnetTransformationException() { + super(); + } + + public DnetTransformationException( + final String message, + final Throwable cause, + final boolean enableSuppression, + final boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public DnetTransformationException(final String message, final Throwable cause) { + super(message, cause); + } + + public DnetTransformationException(final String message) { + super(message); + } + + public DnetTransformationException(final Throwable cause) { + super(cause); + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformFunction.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformFunction.java deleted file mode 100644 index f4bf78e18..000000000 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformFunction.java +++ /dev/null @@ -1,74 +0,0 @@ - -package eu.dnetlib.dhp.transformation; - -import java.io.ByteArrayInputStream; -import java.io.StringWriter; -import java.util.Map; - -import javax.xml.transform.stream.StreamSource; - -import org.apache.spark.api.java.function.MapFunction; -import org.apache.spark.util.LongAccumulator; - -import eu.dnetlib.dhp.model.mdstore.MetadataRecord; -import eu.dnetlib.dhp.transformation.functions.Cleaner; -import eu.dnetlib.dhp.transformation.vocabulary.Vocabulary; -import net.sf.saxon.s9api.*; - -public class TransformFunction implements MapFunction { - - private final LongAccumulator totalItems; - private final LongAccumulator errorItems; - private final LongAccumulator transformedItems; - private final String transformationRule; - private final Cleaner cleanFunction; - - private final long dateOfTransformation; - - public TransformFunction( - LongAccumulator totalItems, - LongAccumulator errorItems, - LongAccumulator transformedItems, - final String transformationRule, - long dateOfTransformation, - final Map vocabularies) - throws Exception { - this.totalItems = totalItems; - this.errorItems = errorItems; - this.transformedItems = transformedItems; - this.transformationRule = transformationRule; - this.dateOfTransformation = dateOfTransformation; - cleanFunction = new Cleaner(vocabularies); - } - - @Override - public MetadataRecord call(MetadataRecord value) { - totalItems.add(1); - try { - Processor processor = new Processor(false); - processor.registerExtensionFunction(cleanFunction); - final XsltCompiler comp = processor.newXsltCompiler(); - XsltExecutable xslt = comp - .compile(new StreamSource(new ByteArrayInputStream(transformationRule.getBytes()))); - XdmNode source = processor - .newDocumentBuilder() - .build(new StreamSource(new ByteArrayInputStream(value.getBody().getBytes()))); - XsltTransformer trans = xslt.load(); - trans.setInitialContextNode(source); - final StringWriter output = new StringWriter(); - Serializer out = processor.newSerializer(output); - out.setOutputProperty(Serializer.Property.METHOD, "xml"); - out.setOutputProperty(Serializer.Property.INDENT, "yes"); - trans.setDestination(out); - trans.transform(); - final String xml = output.toString(); - value.setBody(xml); - value.setDateOfTransformation(dateOfTransformation); - transformedItems.add(1); - return value; - } catch (Throwable e) { - errorItems.add(1); - return null; - } - } -} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java index 8737d36ef..6e07e5173 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java @@ -9,9 +9,15 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import org.apache.commons.cli.*; +import eu.dnetlib.dhp.aggregation.common.AggregationCounter; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; +import eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.spark.SparkConf; +import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoder; import org.apache.spark.sql.Encoders; @@ -25,9 +31,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.collection.GenerateNativeStoreSparkJob; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; -import eu.dnetlib.dhp.transformation.vocabulary.Vocabulary; import eu.dnetlib.dhp.transformation.vocabulary.VocabularyHelper; import eu.dnetlib.dhp.utils.DHPUtils; import eu.dnetlib.message.Message; @@ -57,65 +61,39 @@ public class TransformSparkJobNode { final String inputPath = parser.get("input"); final String outputPath = parser.get("output"); + // TODO this variable will be used after implementing Messaging with DNet Aggregator final String workflowId = parser.get("workflowId"); - final String trasformationRule = extractXSLTFromTR( - Objects.requireNonNull(DHPUtils.decompressString(parser.get("transformationRule")))); - final String rabbitUser = parser.get("rabbitUser"); - final String rabbitPassword = parser.get("rabbitPassword"); - final String rabbitHost = parser.get("rabbitHost"); - final String rabbitReportQueue = parser.get("rabbitReportQueue"); - final long dateOfCollection = new Long(parser.get("dateOfCollection")); - final boolean test = parser.get("isTest") == null ? false : Boolean.valueOf(parser.get("isTest")); + final String isLookupUrl = parser.get("isLookupUrl"); + log.info(String.format("isLookupUrl: %s", isLookupUrl)); + + final ISLookUpService isLookupService = ISLookupClientFactory.getLookUpService(isLookupUrl); SparkConf conf = new SparkConf(); runWithSparkSession( conf, isSparkSessionManaged, - spark -> { - final Encoder encoder = Encoders.bean(MetadataRecord.class); - final Dataset mdstoreInput = spark.read().format("parquet").load(inputPath).as(encoder); - final LongAccumulator totalItems = spark.sparkContext().longAccumulator("TotalItems"); - final LongAccumulator errorItems = spark.sparkContext().longAccumulator("errorItems"); - final LongAccumulator transformedItems = spark.sparkContext().longAccumulator("transformedItems"); - final Map vocabularies = new HashMap<>(); - vocabularies.put("dnet:languages", VocabularyHelper.getVocabularyFromAPI("dnet:languages")); - final TransformFunction transformFunction = new TransformFunction( - totalItems, - errorItems, - transformedItems, - trasformationRule, - dateOfCollection, - vocabularies); - mdstoreInput.map(transformFunction, encoder).write().format("parquet").save(outputPath); - if (rabbitHost != null) { - System.out.println("SEND FINAL REPORT"); - final Map reportMap = new HashMap<>(); - reportMap.put("inputItem", "" + totalItems.value()); - reportMap.put("invalidRecords", "" + errorItems.value()); - reportMap.put("mdStoreSize", "" + transformedItems.value()); - System.out.println(new Message(workflowId, "Transform", MessageType.REPORT, reportMap)); - if (!test) { - final MessageManager manager = new MessageManager(rabbitHost, rabbitUser, rabbitPassword, false, - false, - null); - manager - .sendMessage( - new Message(workflowId, "Transform", MessageType.REPORT, reportMap), - rabbitReportQueue, - true, - false); - manager.close(); - } - } - }); - + spark -> transformRecords(parser.getObjectMap(), isLookupService, spark, inputPath, outputPath)); } - private static String extractXSLTFromTR(final String tr) throws DocumentException { - SAXReader reader = new SAXReader(); - Document document = reader.read(new ByteArrayInputStream(tr.getBytes())); - Node node = document.selectSingleNode("//CODE/*[local-name()='stylesheet']"); - return node.asXML(); + + public static void transformRecords(final Mapargs, final ISLookUpService isLookUpService, final SparkSession spark, final String inputPath, final String outputPath) throws DnetTransformationException { + + final LongAccumulator totalItems = spark.sparkContext().longAccumulator("TotalItems"); + final LongAccumulator errorItems = spark.sparkContext().longAccumulator("errorItems"); + final LongAccumulator transformedItems = spark.sparkContext().longAccumulator("transformedItems"); + final AggregationCounter ct = new AggregationCounter(totalItems, errorItems,transformedItems ); + final Encoder encoder = Encoders.bean(MetadataRecord.class); + final Dataset mdstoreInput = spark.read().format("parquet").load(inputPath).as(encoder); + final MapFunction XSLTTransformationFunction = TransformationFactory.getTransformationPlugin(args,ct, isLookUpService); + mdstoreInput.map(XSLTTransformationFunction, encoder).write().save(outputPath); + + log.info("Transformed item "+ ct.getProcessedItems().count()); + log.info("Total item "+ ct.getTotalItems().count()); + log.info("Transformation Error item "+ ct.getErrorItems().count()); } + + + + } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java new file mode 100644 index 000000000..0296458a5 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java @@ -0,0 +1,62 @@ +package eu.dnetlib.dhp.transformation; + +import eu.dnetlib.dhp.aggregation.common.AggregationCounter; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; +import eu.dnetlib.dhp.model.mdstore.MetadataRecord; +import eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import org.apache.commons.lang3.StringUtils; +import org.apache.spark.api.java.function.MapFunction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; + +public class TransformationFactory { + + private static final Logger log = LoggerFactory.getLogger(TransformationFactory.class); + public static final String TRULE_XQUERY = "for $x in collection('/db/DRIVER/TransformationRuleDSResources/TransformationRuleDSResourceType') where $x//TITLE = \"%s\" return $x//CODE/text()"; + + + public static MapFunction getTransformationPlugin(final Map jobArgument, final AggregationCounter counters, final ISLookUpService isLookupService) throws DnetTransformationException { + + try { + final String transformationPlugin = jobArgument.get("transformationPlugin"); + + log.info("Transformation plugin required "+transformationPlugin); + switch (transformationPlugin) { + case "XSLT_TRANSFORM": { + final String transformationRuleName = jobArgument.get("transformationRule"); + if (StringUtils.isBlank(transformationRuleName)) + throw new DnetTransformationException("Missing Parameter transformationRule"); + final VocabularyGroup vocabularies = VocabularyGroup.loadVocsFromIS(isLookupService); + + final String transformationRule = queryTransformationRuleFromIS(transformationRuleName, isLookupService); + + final long dateOfTransformation = new Long(jobArgument.get("dateOfTransformation")); + return new XSLTTransformationFunction(counters,transformationRule,dateOfTransformation,vocabularies); + + } + default: + throw new DnetTransformationException("transformation plugin does not exists for " + transformationPlugin); + + } + + } catch (Throwable e) { + throw new DnetTransformationException(e); + } + } + + private static String queryTransformationRuleFromIS(final String transformationRuleName, final ISLookUpService isLookUpService) throws Exception { + final String query = String.format(TRULE_XQUERY, transformationRuleName); + log.info("asking query to IS: "+ query); + List result = isLookUpService.quickSearchProfile(query); + + if (result==null || result.isEmpty()) + throw new DnetTransformationException("Unable to find transformation rule with name: "+ transformationRuleName); + return result.get(0); + } + + +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/functions/Cleaner.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java similarity index 61% rename from dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/functions/Cleaner.java rename to dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java index 7f9b6646c..2c6d776af 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/functions/Cleaner.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java @@ -1,19 +1,17 @@ -package eu.dnetlib.dhp.transformation.functions; +package eu.dnetlib.dhp.transformation.xslt; -import java.util.Map; -import java.util.Optional; -import eu.dnetlib.dhp.transformation.vocabulary.Term; -import eu.dnetlib.dhp.transformation.vocabulary.Vocabulary; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; +import eu.dnetlib.dhp.schema.oaf.Qualifier; import net.sf.saxon.s9api.*; import scala.Serializable; public class Cleaner implements ExtensionFunction, Serializable { - private final Map vocabularies; + private final VocabularyGroup vocabularies; - public Cleaner(Map vocabularies) { + public Cleaner(final VocabularyGroup vocabularies) { this.vocabularies = vocabularies; } @@ -39,14 +37,9 @@ public class Cleaner implements ExtensionFunction, Serializable { public XdmValue call(XdmValue[] xdmValues) throws SaxonApiException { final String currentValue = xdmValues[0].itemAt(0).getStringValue(); final String vocabularyName = xdmValues[1].itemAt(0).getStringValue(); - Optional cleanedValue = vocabularies - .get(vocabularyName) - .getTerms() - .stream() - .filter(it -> it.getNativeName().equalsIgnoreCase(currentValue)) - .findAny(); + Qualifier cleanedValue = vocabularies.getSynonymAsQualifier(vocabularyName, currentValue); return new XdmAtomicValue( - cleanedValue.isPresent() ? cleanedValue.get().getCode() : currentValue); + cleanedValue != null ? cleanedValue.getClassid() : currentValue); } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java new file mode 100644 index 000000000..c02b83345 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java @@ -0,0 +1,66 @@ + +package eu.dnetlib.dhp.transformation.xslt; + +import eu.dnetlib.dhp.aggregation.common.AggregationCounter; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; +import eu.dnetlib.dhp.model.mdstore.MetadataRecord; +import net.sf.saxon.s9api.*; +import org.apache.spark.api.java.function.MapFunction; + +import javax.xml.transform.stream.StreamSource; +import java.io.ByteArrayInputStream; +import java.io.StringWriter; + +public class XSLTTransformationFunction implements MapFunction { + + private final AggregationCounter aggregationCounter; + + private final String transformationRule; + + private final Cleaner cleanFunction; + + private final long dateOfTransformation; + + public XSLTTransformationFunction( + final AggregationCounter aggregationCounter, + final String transformationRule, + long dateOfTransformation, + final VocabularyGroup vocabularies) + throws Exception { + this.aggregationCounter = aggregationCounter; + this.transformationRule = transformationRule; + this.dateOfTransformation = dateOfTransformation; + cleanFunction = new Cleaner(vocabularies); + } + + @Override + public MetadataRecord call(MetadataRecord value) { + aggregationCounter.getTotalItems().add(1); + try { + Processor processor = new Processor(false); + processor.registerExtensionFunction(cleanFunction); + final XsltCompiler comp = processor.newXsltCompiler(); + XsltExecutable xslt = comp + .compile(new StreamSource(new ByteArrayInputStream(transformationRule.getBytes()))); + XdmNode source = processor + .newDocumentBuilder() + .build(new StreamSource(new ByteArrayInputStream(value.getBody().getBytes()))); + XsltTransformer trans = xslt.load(); + trans.setInitialContextNode(source); + final StringWriter output = new StringWriter(); + Serializer out = processor.newSerializer(output); + out.setOutputProperty(Serializer.Property.METHOD, "xml"); + out.setOutputProperty(Serializer.Property.INDENT, "yes"); + trans.setDestination(out); + trans.transform(); + final String xml = output.toString(); + value.setBody(xml); + value.setDateOfTransformation(dateOfTransformation); + aggregationCounter.getProcessedItems().add(1); + return value; + } catch (Throwable e) { + aggregationCounter.getErrorItems().add(1); + return null; + } + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/transformation_input_parameters.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/transformation_input_parameters.json index 4bb5fd56a..fd2a96ea0 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/transformation_input_parameters.json +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/transformation_input_parameters.json @@ -7,7 +7,7 @@ }, { "paramName": "d", - "paramLongName": "dateOfCollection", + "paramLongName": "dateOfTransformation", "paramDescription": "the date when the record has been stored", "paramRequired": true }, @@ -36,39 +36,9 @@ "paramRequired": true }, { - "paramName": "ru", - "paramLongName": "rabbitUser", - "paramDescription": "the user to connect with RabbitMq for messaging", + "paramName": "tp", + "paramLongName": "transformationPlugin", + "paramDescription": "the transformation plugin to apply", "paramRequired": true - }, - { - "paramName": "rp", - "paramLongName": "rabbitPassword", - "paramDescription": "the password to connect with RabbitMq for messaging", - "paramRequired": true - }, - { - "paramName": "rh", - "paramLongName": "rabbitHost", - "paramDescription": "the host of the RabbitMq server", - "paramRequired": true - }, - { - "paramName": "ro", - "paramLongName": "rabbitOngoingQueue", - "paramDescription": "the name of the ongoing queue", - "paramRequired": true - }, - { - "paramName": "rr", - "paramLongName": "rabbitReportQueue", - "paramDescription": "the name of the report queue", - "paramRequired": true - }, - { - "paramName": "t", - "paramLongName": "isTest", - "paramDescription": "the name of the report queue", - "paramRequired": false } ] \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java index 98c8cf66c..5479e0b57 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java @@ -2,15 +2,23 @@ package eu.dnetlib.dhp.transformation; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.lenient; +import java.io.IOException; import java.io.StringWriter; import java.nio.file.Files; import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.xml.transform.stream.StreamSource; +import eu.dnetlib.dhp.aggregation.common.AggregationCounter; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; +import eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; import org.apache.spark.sql.SparkSession; @@ -18,28 +26,34 @@ import org.apache.spark.util.LongAccumulator; import org.dom4j.Document; import org.dom4j.Node; import org.dom4j.io.SAXReader; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - import eu.dnetlib.dhp.collection.CollectionJobTest; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; -import eu.dnetlib.dhp.transformation.functions.Cleaner; -import eu.dnetlib.dhp.transformation.vocabulary.Vocabulary; -import eu.dnetlib.dhp.transformation.vocabulary.VocabularyHelper; -import eu.dnetlib.dhp.utils.DHPUtils; -import net.sf.saxon.s9api.*; @ExtendWith(MockitoExtension.class) public class TransformationJobTest { private static SparkSession spark; + @Mock + private ISLookUpService isLookUpService; + + private VocabularyGroup vocabularies; + + @BeforeEach + public void setUp() throws ISLookUpException, IOException { + lenient().when(isLookUpService.quickSearchProfile(VocabularyGroup.VOCABULARIES_XQUERY)).thenReturn(vocs()); + + lenient() + .when(isLookUpService.quickSearchProfile(VocabularyGroup.VOCABULARY_SYNONYMS_XQUERY)) + .thenReturn(synonyms()); + vocabularies = VocabularyGroup.loadVocsFromIS(isLookUpService); + } + @BeforeAll public static void beforeAll() { SparkConf conf = new SparkConf(); @@ -53,64 +67,51 @@ public class TransformationJobTest { spark.stop(); } - @Mock - private LongAccumulator accumulator; @Test + @DisplayName("Test Transform Single XML using XSLTTransformator") public void testTransformSaxonHE() throws Exception { - Map vocabularies = new HashMap<>(); - vocabularies.put("dnet:languages", VocabularyHelper.getVocabularyFromAPI("dnet:languages")); - Cleaner cleanFunction = new Cleaner(vocabularies); - Processor proc = new Processor(false); - proc.registerExtensionFunction(cleanFunction); - final XsltCompiler comp = proc.newXsltCompiler(); - XsltExecutable exp = comp - .compile( - new StreamSource( - this.getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/ext_simple.xsl"))); - XdmNode source = proc - .newDocumentBuilder() - .build( - new StreamSource( - this.getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/input.xml"))); - XsltTransformer trans = exp.load(); - trans.setInitialContextNode(source); - final StringWriter output = new StringWriter(); - Serializer out = proc.newSerializer(output); - out.setOutputProperty(Serializer.Property.METHOD, "xml"); - out.setOutputProperty(Serializer.Property.INDENT, "yes"); - trans.setDestination(out); - trans.transform(); - System.out.println(output.toString()); + // We Set the input Record getting the XML from the classpath + final MetadataRecord mr = new MetadataRecord(); + mr.setBody(IOUtils.toString(getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/input.xml"))); + + + // We Load the XSLT trasformation Rule from the classpath + XSLTTransformationFunction tr = loadTransformationRule("/eu/dnetlib/dhp/transform/ext_simple.xsl"); + + //Print the record + System.out.println(tr.call(mr).getBody()); + //TODO Create significant Assert + } + + + @DisplayName("Test TransformSparkJobNode.main") @Test public void transformTest(@TempDir Path testDir) throws Exception { + final String mdstore_input = this.getClass().getResource("/eu/dnetlib/dhp/transform/mdstorenative").getFile(); final String mdstore_output = testDir.toString() + "/version"; - final String xslt = DHPUtils - .compressString( - IOUtils - .toString( - this.getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/tr.xml"))); - TransformSparkJobNode - .main( - new String[] { - "-issm", "true", - "-i", mdstore_input, - "-o", mdstore_output, - "-d", "1", - "-w", "1", - "-tr", xslt, - "-t", "true", - "-ru", "", - "-rp", "", - "-rh", "", - "-ro", "", - "-rr", "" - }); + + + mockupTrasformationRule("simpleTRule","/eu/dnetlib/dhp/transform/ext_simple.xsl"); + +// final String arguments = "-issm true -i %s -o %s -d 1 -w 1 -tp XSLT_TRANSFORM -tr simpleTRule"; + + final Map parameters = Stream.of(new String[][] { + { "dateOfTransformation", "1234" }, + { "transformationPlugin", "XSLT_TRANSFORM" }, + { "transformationRule", "simpleTRule" }, + + }).collect(Collectors.toMap(data -> data[0], data -> data[1])); + + TransformSparkJobNode.transformRecords(parameters,isLookUpService,spark,mdstore_input, mdstore_output); + + + // TODO introduce useful assertions } @@ -127,39 +128,27 @@ public class TransformationJobTest { Files.deleteIfExists(tempDirWithPrefix); } - @Test - public void testTransformFunction() throws Exception { - SAXReader reader = new SAXReader(); - Document document = reader.read(this.getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/tr.xml")); - Node node = document.selectSingleNode("//CODE/*[local-name()='stylesheet']"); - final String xslt = node.asXML(); - Map vocabularies = new HashMap<>(); - vocabularies.put("dnet:languages", VocabularyHelper.getVocabularyFromAPI("dnet:languages")); - TransformFunction tf = new TransformFunction(accumulator, accumulator, accumulator, xslt, 1, vocabularies); + private void mockupTrasformationRule(final String trule, final String path)throws Exception { + final String trValue = IOUtils.toString(this.getClass().getResourceAsStream(path)); - MetadataRecord record = new MetadataRecord(); - record - .setBody( - IOUtils - .toString( - this.getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/input.xml"))); - - final MetadataRecord result = tf.call(record); - assertNotNull(result.getBody()); - - System.out.println(result.getBody()); + lenient().when(isLookUpService.quickSearchProfile(String.format(TransformationFactory.TRULE_XQUERY,trule))) + .thenReturn(Collections.singletonList(trValue)); } - @Test - public void extractTr() throws Exception { + private XSLTTransformationFunction loadTransformationRule(final String path) throws Exception { + final String trValue = IOUtils.toString(this.getClass().getResourceAsStream(path)); + final LongAccumulator la = new LongAccumulator(); + return new XSLTTransformationFunction(new AggregationCounter(la,la,la),trValue, 0,vocabularies); + } - final String xmlTr = IOUtils.toString(this.getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/tr.xml")); + private List vocs() throws IOException { + return IOUtils + .readLines(TransformationJobTest.class.getResourceAsStream("/eu/dnetlib/dhp/transform/terms.txt")); + } - SAXReader reader = new SAXReader(); - Document document = reader.read(this.getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/tr.xml")); - Node node = document.selectSingleNode("//CODE/*[local-name()='stylesheet']"); - - System.out.println(node.asXML()); + private List synonyms() throws IOException { + return IOUtils + .readLines(TransformationJobTest.class.getResourceAsStream("/eu/dnetlib/dhp/transform/synonyms.txt")); } } diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl index f22db961b..9e5f84c11 100644 --- a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl @@ -1,15 +1,16 @@ + exclude-result-prefixes="xsl vocabulary"> - - + + diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input.xml b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input.xml index 8760d3117..8efb3c487 100644 --- a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input.xml +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input.xml @@ -1,37 +1,68 @@ - -
- oai:research.chalmers.se:243692 - 2018-01-25T18:04:43Z - openaire -
- - - Incipient Berezinskii-Kosterlitz-Thouless transition in two-dimensional coplanar Josephson junctions - https://research.chalmers.se/en/publication/243692 - 2016 - Massarotti, D. - Jouault, B. - Rouco, V. - Charpentier, Sophie - Bauch, Thilo - Michon, A. - De Candia, A. - Lucignano, P. - Lombardi, Floriana - Tafuri, F. - Tagliacozzo, A. - Acoli - Abkhazian - Condensed Matter Physics - Superconducting hybrid junctions are revealing a variety of effects. Some of them are due to the special layout of these devices, which often use a coplanar configuration with relatively large barrier channels and the possibility of hosting Pearl vortices. A Josephson junction with a quasi-ideal two-dimensional barrier has been realized by growing graphene on SiC with Al electrodes. Chemical vapor deposition offers centimeter size monolayer areas where it is possible to realize a comparative analysis of different devices with nominally the same barrier. In samples with a graphene gap below 400 nm, we have found evidence of Josephson coherence in the presence of an incipient Berezinskii-Kosterlitz-Thouless transition. When the magnetic field is cycled, a remarkable hysteretic collapse and revival of the Josephson supercurrent occurs. Similar hysteresis are found in granular systems and are usually justified within the Bean critical state model (CSM). We show that the CSM, with appropriate account for the low-dimensional geometry, can partly explain the odd features measured in these junctions. - info:eu-repo/grantAgreement/EC/FP7/604391//Graphene-Based Revolutions in ICT And Beyond (Graphene Flagship)/ - info:eu-repo/semantics/altIdentifier/doi/10.1103/PhysRevB.94.054525 - info:eu-repo/semantics/article - Physical Review B vol.94(2016) - info:eu-repo/semantics/openAccess + + + + od______2294::00029b7f0a2a7e090e55b625a9079d83 + oai:pub.uni-bielefeld.de:2578942 + 2018-11-23T15:15:33.974+01:00 + od______2294 + oai:pub.uni-bielefeld.de:2578942 + 2018-07-24T13:01:16Z + conference + ddc:000 + conferenceFtxt + driver + open_access + + + + Mobile recommendation agents making online use of visual attention information at the point of sale + Pfeiffer, Thies + Pfeiffer, Jella + Meißner, Martin + Davis, Fred + Riedl, René + Jan, vom Brocke + Léger, Pierre-Majorique + Randolph, Adriane + Mobile Cognitive Assistance Systems + Information Systems + ddc:000 + We aim to utilize online information about visual attention for developing mobile recommendation agents (RAs) for use at the point of sale. Up to now, most RAs are focussed exclusively at personalization in an e-commerce setting. Very little is known, however, about mobile RAs that offer information and assistance at the point of sale based on individual-level feature based preference models (Murray and Häubl 2009). Current attempts provide information about products at the point of sale by manually scanning barcodes or using RFID (Kowatsch et al. 2011, Heijden 2005), e.g. using specific apps for smartphones. We argue that an online access to the current visual attention of the user offers a much larger potential. Integrating mobile eye tracking into ordinary glasses would yield a direct benefit of applying neuroscience methods in the user’s everyday life. First, learning from consumers’ attentional processes over time and adapting recommendations based on this learning allows us to provide very accurate and relevant recommendations, potentially increasing the perceived usefulness. Second, our proposed system needs little explicit user input (no scanning or navigation on screen) making it easy to use. Thus, instead of learning from click behaviour and past customer ratings, as it is the case in the e-commerce setting, the mobile RA learns from eye movements by participating online in every day decision processes. We argue that mobile RAs should be built based on current research in human judgment and decision making (Murray et al. 2010). In our project, we therefore follow a two-step approach: In the empirical basic research stream, we aim to understand the user’s interaction with the product shelf: the actions and patterns of user’s behaviour (eye movements, gestures, approaching a product closer) and their correspondence to the user’s informational needs. In the empirical system development stream, we create prototypes of mobile RAs and test experimentally the factors that influence the user’s adoption. For example, we suggest that a user’s involvement in the process, such as a need for exact nutritional information or for assistance (e.g., reading support for elderly) will influence the user’s intention to use such as system. The experiments are conducted both in our immersive virtual reality supermarket presented in a CAVE, where we can also easily display information to the user and track the eye movement in great accuracy, as well as in real-world supermarkets (see Figure 1), so that the findings can be better generalized to natural decision situations (Gidlöf et al. 2013). In a first pilot study with five randomly chosen participants in a supermarket, we evaluated which sort of mobile RAs consumers favour in order to get a first impression of the user’s acceptance of the technology. Figure 1 shows an excerpt of one consumer’s eye movements during a decision process. First results show long eye cascades and short fixations on many products in situations where users are uncertain and in need for support. Furthermore, we find a surprising acceptance of the technology itself throughout all ages (23 – 61 years). At the same time, consumers express serious fear of being manipulated by such a technology. For that reason, they strongly prefer the information to be provided by trusted third party or shared with family members and friends (see also Murray and Häubl 2009). Our pilot will be followed by a larger field experiment in March in order to learn more about factors that influence the user’s acceptance as well as the eye movement patterns that reflect typical phases of decision processes and indicate the need for support by a RA. + 2013 + info:eu-repo/semantics/conferenceObject + doc-type:conferenceObject + text + https://pub.uni-bielefeld.de/record/2578942 + https://pub.uni-bielefeld.de/download/2578942/2602478 + Pfeiffer T, Pfeiffer J, Meißner M. Mobile recommendation agents making online use of visual attention information at the point of sale. In: Davis F, Riedl R, Jan vom B, Léger P-M, Randolph A, eds. Proceedings of the Gmunden Retreat on NeuroIS 2013. 2013: 3-3. eng - Researchers - application/pdf + info:eu-repo/semantics/openAccess -
+ + + + http://pub.uni-bielefeld.de/oai + oai:pub.uni-bielefeld.de:2578942 + 2018-07-24T13:01:16Z + http://www.openarchives.org/OAI/2.0/oai_dc/ + + + + false + false + 0.9 + + + + +
diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/synonyms.txt b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/synonyms.txt new file mode 100644 index 000000000..729296522 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/synonyms.txt @@ -0,0 +1,1234 @@ +dnet:access_modes @=@ CLOSED @=@ http://purl.org/coar/access_right/c_14cb +dnet:access_modes @=@ CLOSED @=@ info:eu-repo/semantics/closedAccess +dnet:access_modes @=@ EMBARGO @=@ http://purl.org/coar/access_right/c_f1cf +dnet:access_modes @=@ EMBARGO @=@ info:eu-repo/semantics/embargoedAccess +dnet:access_modes @=@ OPEN @=@ Creative Commons License [CC BY-NC-ND] http://creativecommons.org/licenses/by-nc-nd/3.0/de/ +dnet:access_modes @=@ OPEN @=@ Creative commons +dnet:access_modes @=@ OPEN @=@ http://creativecommons.org/licenses/by-nc-nd/3.0/ +dnet:access_modes @=@ OPEN @=@ http://creativecommons.org/licenses/by-nc/3.0/ +dnet:access_modes @=@ OPEN @=@ http://creativecommons.org/licenses/by-sa/3.0/ +dnet:access_modes @=@ OPEN @=@ http://creativecommons.org/licenses/by-sa/4.0/ +dnet:access_modes @=@ OPEN @=@ http://creativecommons.org/licenses/by/3.0/ +dnet:access_modes @=@ OPEN @=@ http://creativecommons.org/licenses/by/3.0/us/ +dnet:access_modes @=@ OPEN @=@ http://creativecommons.org/licenses/by/4.0/ +dnet:access_modes @=@ OPEN @=@ http://creativecommons.org/publicdomain/zero/1.0/ +dnet:access_modes @=@ OPEN @=@ http://creativecommons.org/publicdomain/zero/1.0/ & http://www.canadensys.net/norms +dnet:access_modes @=@ OPEN @=@ http://purl.org/coar/access_right/c_abf2 +dnet:access_modes @=@ OPEN @=@ https://creativecommons.org/licenses/by-nc/4.0/ +dnet:access_modes @=@ OPEN @=@ info:eu-repo/semantics/openAccess +dnet:access_modes @=@ OPEN @=@ open_access +dnet:access_modes @=@ RESTRICTED @=@ http://purl.org/coar/access_right/c_16ec +dnet:access_modes @=@ RESTRICTED @=@ info:eu-repo/semantics/restrictedAccess +dnet:compatibilityLevel @=@ openaire-pub_4.0 @=@ openaire4.0 +dnet:subject_classification_typologies @=@ jel @=@ jelElement +dnet:publication_resource @=@ 0018 @=@ Comment/debate +dnet:publication_resource @=@ 0018 @=@ http://purl.org/coar/resource_type/c_1162 +dnet:publication_resource @=@ 0018 @=@ info:eu-repo/semantics/annotation +dnet:publication_resource @=@ 0001 @=@ A1 Alkuperäisartikkeli tieteellisessä aikakauslehdessä +dnet:publication_resource @=@ 0001 @=@ Article +dnet:publication_resource @=@ 0001 @=@ Article (author) +dnet:publication_resource @=@ 0001 @=@ Article - letter to the editor +dnet:publication_resource @=@ 0001 @=@ Article / Letter to editor +dnet:publication_resource @=@ 0001 @=@ Article / Letter to the editor +dnet:publication_resource @=@ 0001 @=@ Article / Newspaper +dnet:publication_resource @=@ 0001 @=@ Article in journal +dnet:publication_resource @=@ 0001 @=@ Article in monograph or in proceedings +dnet:publication_resource @=@ 0001 @=@ Article in proceedings +dnet:publication_resource @=@ 0001 @=@ Article-letter to the editor +dnet:publication_resource @=@ 0001 @=@ Article/Letter to editor +dnet:publication_resource @=@ 0001 @=@ Articolo +dnet:publication_resource @=@ 0001 @=@ Artículo +dnet:publication_resource @=@ 0001 @=@ Aufsatz +dnet:publication_resource @=@ 0001 @=@ Clinical Study +dnet:publication_resource @=@ 0001 @=@ Institutional Series +dnet:publication_resource @=@ 0001 @=@ International Journal +dnet:publication_resource @=@ 0001 @=@ International Journal Abstract +dnet:publication_resource @=@ 0001 @=@ International Journal ISI/JCR +dnet:publication_resource @=@ 0001 @=@ Journal (full / special issue) +dnet:publication_resource @=@ 0001 @=@ Journal Article/Review +dnet:publication_resource @=@ 0001 @=@ Journal article +dnet:publication_resource @=@ 0001 @=@ Journal article (on-line or printed) +dnet:publication_resource @=@ 0001 @=@ Journal articles +dnet:publication_resource @=@ 0001 @=@ Journal paper +dnet:publication_resource @=@ 0001 @=@ National Journal +dnet:publication_resource @=@ 0001 @=@ Original article (non peer-reviewed) +dnet:publication_resource @=@ 0001 @=@ Original article (peer-reviewed) +dnet:publication_resource @=@ 0001 @=@ Peer-reviewed Article +dnet:publication_resource @=@ 0001 @=@ Published Journal Article +dnet:publication_resource @=@ 0001 @=@ Research Article +dnet:publication_resource @=@ 0001 @=@ Review article (non peer-reviewed) +dnet:publication_resource @=@ 0001 @=@ Review article (peer-reviewed) +dnet:publication_resource @=@ 0001 @=@ Volumes Edited / Special Issues +dnet:publication_resource @=@ 0001 @=@ article in non peer-reviewed journal +dnet:publication_resource @=@ 0001 @=@ article in peer-reviewed journal +dnet:publication_resource @=@ 0001 @=@ article-commentary +dnet:publication_resource @=@ 0001 @=@ article_site_web +dnet:publication_resource @=@ 0001 @=@ doc-type:Journal Article +dnet:publication_resource @=@ 0001 @=@ doc-type:article +dnet:publication_resource @=@ 0001 @=@ http://purl.org/coar/resource_type/c_2df8fbb1 +dnet:publication_resource @=@ 0001 @=@ http://purl.org/coar/resource_type/c_545b +dnet:publication_resource @=@ 0001 @=@ http://purl.org/coar/resource_type/c_6501 +dnet:publication_resource @=@ 0001 @=@ http://purl.org/coar/resource_type/c_7877 +dnet:publication_resource @=@ 0001 @=@ in-brief +dnet:publication_resource @=@ 0001 @=@ info:eu-repo/semantics/article +dnet:publication_resource @=@ 0001 @=@ journal-article +dnet:publication_resource @=@ 0001 @=@ journalArticle +dnet:publication_resource @=@ 0001 @=@ journal_article +dnet:publication_resource @=@ 0001 @=@ letter +dnet:publication_resource @=@ 0001 @=@ non peer-reviewed article +dnet:publication_resource @=@ 0001 @=@ partial-retraction +dnet:publication_resource @=@ 0001 @=@ proceeding with peer review +dnet:publication_resource @=@ 0001 @=@ publication-article +dnet:publication_resource @=@ 0001 @=@ rapid-communication +dnet:publication_resource @=@ 0001 @=@ reply +dnet:publication_resource @=@ 0001 @=@ research-article +dnet:publication_resource @=@ 0001 @=@ retraction +dnet:publication_resource @=@ 0001 @=@ review-article +dnet:publication_resource @=@ 0001 @=@ text (article) +dnet:publication_resource @=@ 0001 @=@ Статья +dnet:publication_resource @=@ 0001 @=@ ArticleArtikel +dnet:publication_resource @=@ 0033 @=@ AUDIOVISUAL_DOCUMENT +dnet:publication_resource @=@ 0033 @=@ Audiovisual/Audiovisual +dnet:publication_resource @=@ 0033 @=@ http://purl.org/coar/resource_type/c_c513 +dnet:publication_resource @=@ 0008 @=@ Bachelor's +dnet:publication_resource @=@ 0008 @=@ Bachelor's Degree +dnet:publication_resource @=@ 0008 @=@ Bachelors Thesis +dnet:publication_resource @=@ 0008 @=@ Proyecto fin de carrera +dnet:publication_resource @=@ 0008 @=@ Undergraduate Thesis +dnet:publication_resource @=@ 0008 @=@ http://purl.org/coar/resource_type/c_7a1f +dnet:publication_resource @=@ 0008 @=@ info:eu-repo/semantics/bachelorThesis +dnet:publication_resource @=@ 0008 @=@ выпускная бакалаврская работа +dnet:publication_resource @=@ 0002 @=@ Book (monograph) +dnet:publication_resource @=@ 0002 @=@ Book (non peer-reviewed) +dnet:publication_resource @=@ 0002 @=@ Book (peer-reviewed) +dnet:publication_resource @=@ 0002 @=@ Book - monograph - editorial book +dnet:publication_resource @=@ 0002 @=@ Book Section +dnet:publication_resource @=@ 0002 @=@ Book as author +dnet:publication_resource @=@ 0002 @=@ Buch +dnet:publication_resource @=@ 0002 @=@ International Book/Monograph +dnet:publication_resource @=@ 0002 @=@ Libro +dnet:publication_resource @=@ 0002 @=@ Monografia +dnet:publication_resource @=@ 0002 @=@ Monograph +dnet:publication_resource @=@ 0002 @=@ National Book/Monograph +dnet:publication_resource @=@ 0002 @=@ atlas +dnet:publication_resource @=@ 0002 @=@ book +dnet:publication_resource @=@ 0002 @=@ book-series +dnet:publication_resource @=@ 0002 @=@ book-set +dnet:publication_resource @=@ 0002 @=@ book-track +dnet:publication_resource @=@ 0002 @=@ book_series +dnet:publication_resource @=@ 0002 @=@ book_title +dnet:publication_resource @=@ 0002 @=@ doc-type:book +dnet:publication_resource @=@ 0002 @=@ edited-book +dnet:publication_resource @=@ 0002 @=@ http://purl.org/coar/resource_type/c_2f33 +dnet:publication_resource @=@ 0002 @=@ info:eu-repo/semantics/book +dnet:publication_resource @=@ 0002 @=@ ouvrage +dnet:publication_resource @=@ 0002 @=@ publication-book +dnet:publication_resource @=@ 0002 @=@ reference-book +dnet:publication_resource @=@ 0002 @=@ scientific book +dnet:publication_resource @=@ 0002 @=@ Монография +dnet:publication_resource @=@ 0002 @=@ Учебник +dnet:publication_resource @=@ 0037 @=@ clinicalTrial +dnet:publication_resource @=@ 0037 @=@ http://purl.org/coar/resource_type/c_cb28 +dnet:publication_resource @=@ 0022 @=@ collection +dnet:publication_resource @=@ 0004 @=@ A4 Artikkeli konferenssijulkaisussa +dnet:publication_resource @=@ 0004 @=@ Comunicación de congreso +dnet:publication_resource @=@ 0004 @=@ Conference Paper +dnet:publication_resource @=@ 0004 @=@ Conference Paper/Proceeding/Abstract +dnet:publication_resource @=@ 0004 @=@ Conference Proceedings +dnet:publication_resource @=@ 0004 @=@ Conference article +dnet:publication_resource @=@ 0004 @=@ Conference contribution +dnet:publication_resource @=@ 0004 @=@ Conference lecture +dnet:publication_resource @=@ 0004 @=@ Conference or Workshop Item +dnet:publication_resource @=@ 0004 @=@ Conference paper, poster, etc. +dnet:publication_resource @=@ 0004 @=@ Conference papers +dnet:publication_resource @=@ 0004 @=@ Conference report +dnet:publication_resource @=@ 0004 @=@ International Conference +dnet:publication_resource @=@ 0004 @=@ International Conference Abstract/Poster +dnet:publication_resource @=@ 0004 @=@ International Conference ISI/JCR +dnet:publication_resource @=@ 0004 @=@ International Conference communication/abstract/poster +dnet:publication_resource @=@ 0004 @=@ National Conference +dnet:publication_resource @=@ 0004 @=@ National Conference Abstract/Poster +dnet:publication_resource @=@ 0004 @=@ National Conference communication/abstract/poster +dnet:publication_resource @=@ 0004 @=@ PREFACE_PROCEEDINGS +dnet:publication_resource @=@ 0004 @=@ PROCEEDING_PAPER +dnet:publication_resource @=@ 0004 @=@ Papers in Conference Proceedings +dnet:publication_resource @=@ 0004 @=@ Presentación +dnet:publication_resource @=@ 0004 @=@ Proceedings (peer-reviewed) +dnet:publication_resource @=@ 0004 @=@ Proceedings of a Conference +dnet:publication_resource @=@ 0004 @=@ Proceedings paper +dnet:publication_resource @=@ 0004 @=@ Póster +dnet:publication_resource @=@ 0004 @=@ actes_congres +dnet:publication_resource @=@ 0004 @=@ communication_avec_actes +dnet:publication_resource @=@ 0004 @=@ communication_invitee +dnet:publication_resource @=@ 0004 @=@ communication_par_affiche +dnet:publication_resource @=@ 0004 @=@ communication_sans_actes +dnet:publication_resource @=@ 0004 @=@ conference +dnet:publication_resource @=@ 0004 @=@ conference item +dnet:publication_resource @=@ 0004 @=@ conference proceeding +dnet:publication_resource @=@ 0004 @=@ conferenceObject +dnet:publication_resource @=@ 0004 @=@ conference_paper +dnet:publication_resource @=@ 0004 @=@ doc-type:conferenceObject +dnet:publication_resource @=@ 0004 @=@ http://purl.org/coar/resource_type/c_18co +dnet:publication_resource @=@ 0004 @=@ http://purl.org/coar/resource_type/c_18cp +dnet:publication_resource @=@ 0004 @=@ http://purl.org/coar/resource_type/c_5794 +dnet:publication_resource @=@ 0004 @=@ http://purl.org/coar/resource_type/c_6670 +dnet:publication_resource @=@ 0004 @=@ http://purl.org/coar/resource_type/c_c94f +dnet:publication_resource @=@ 0004 @=@ http://purl.org/coar/resource_type/c_f744 +dnet:publication_resource @=@ 0004 @=@ info:eu-repo/semantics/conferenceItem +dnet:publication_resource @=@ 0004 @=@ info:eu-repo/semantics/conferenceObject +dnet:publication_resource @=@ 0004 @=@ invited conference talk +dnet:publication_resource @=@ 0004 @=@ poster +dnet:publication_resource @=@ 0004 @=@ presentation +dnet:publication_resource @=@ 0004 @=@ proceeding, seminar, workshop without peer review +dnet:publication_resource @=@ 0004 @=@ proceedings +dnet:publication_resource @=@ 0004 @=@ proceedings-article +dnet:publication_resource @=@ 0004 @=@ publication-conferencepaper +dnet:publication_resource @=@ 0004 @=@ научный доклад +dnet:publication_resource @=@ 0005 @=@ Newspaper or magazine article +dnet:publication_resource @=@ 0005 @=@ http://purl.org/coar/resource_type/c_998f +dnet:publication_resource @=@ 0005 @=@ info:eu-repo/semantics/contributionToPeriodical +dnet:publication_resource @=@ 0045 @=@ Data Management Plan +dnet:publication_resource @=@ 0045 @=@ Data Management Plan (NSF Generic) +dnet:publication_resource @=@ 0045 @=@ http://purl.org/coar/resource_type/c_ab20 +dnet:publication_resource @=@ 0045 @=@ http://purl.org/spar/fabio/DataManagementPolicy +dnet:publication_resource @=@ 0045 @=@ http://purl.org/spar/fabio/DataManagementPolicyDocument +dnet:publication_resource @=@ 0045 @=@ http://purl.org/spar/fabio/DataMangementPlan +dnet:publication_resource @=@ 0045 @=@ plan de gestión de datos +dnet:publication_resource @=@ 0045 @=@ publication-datamanagementplan +dnet:publication_resource @=@ 0031 @=@ Data Descriptor +dnet:publication_resource @=@ 0031 @=@ DataPaper +dnet:publication_resource @=@ 0031 @=@ data-article +dnet:publication_resource @=@ 0031 @=@ http://purl.org/coar/resource_type/c_beb9 +dnet:publication_resource @=@ 0021 @=@ Dataset/Dataset +dnet:publication_resource @=@ 0021 @=@ Research Data +dnet:publication_resource @=@ 0021 @=@ dataset +dnet:publication_resource @=@ 0021 @=@ http://purl.org/coar/resource_type/c_ddb1 +dnet:publication_resource @=@ 0021 @=@ info:eu-repo/semantics/DDIInstance +dnet:publication_resource @=@ 0021 @=@ info:eu-repo/semantics/datafile +dnet:publication_resource @=@ 0021 @=@ info:eu-repo/semantics/dataset +dnet:publication_resource @=@ 0021 @=@ info:eu-repo/semantics/enhancedObjectFile +dnet:publication_resource @=@ 0006 @=@ Diss +dnet:publication_resource @=@ 0006 @=@ Dissertation +dnet:publication_resource @=@ 0006 @=@ Doctoral +dnet:publication_resource @=@ 0006 @=@ DoctoralThesis +dnet:publication_resource @=@ 0006 @=@ PhD thesis +dnet:publication_resource @=@ 0006 @=@ Tesis +dnet:publication_resource @=@ 0006 @=@ Text.Thesis.Doctoral +dnet:publication_resource @=@ 0006 @=@ Theses +dnet:publication_resource @=@ 0006 @=@ Thesis +dnet:publication_resource @=@ 0006 @=@ Thesis or Dissertation +dnet:publication_resource @=@ 0006 @=@ Thesis.Doctoral +dnet:publication_resource @=@ 0006 @=@ doc-type:doctoralThesis +dnet:publication_resource @=@ 0006 @=@ http://purl.org/coar/resource_type/c_db06 +dnet:publication_resource @=@ 0006 @=@ info:eu-repo/semantics/doctoralThesis +dnet:publication_resource @=@ 0006 @=@ publication-thesis +dnet:publication_resource @=@ 0006 @=@ these +dnet:publication_resource @=@ 0006 @=@ these exercice +dnet:publication_resource @=@ 0023 @=@ Event/Event +dnet:publication_resource @=@ 0023 @=@ event +dnet:publication_resource @=@ 0009 @=@ Departmental Technical Report +dnet:publication_resource @=@ 0009 @=@ Informe Técnico +dnet:publication_resource @=@ 0009 @=@ RESEARCH_REPORT +dnet:publication_resource @=@ 0009 @=@ Tech-Report +dnet:publication_resource @=@ 0009 @=@ Technical Report +dnet:publication_resource @=@ 0009 @=@ http://purl.org/coar/resource_type/c_18gh +dnet:publication_resource @=@ 0009 @=@ publication-technicalnote +dnet:publication_resource @=@ 0009 @=@ research report +dnet:publication_resource @=@ 0024 @=@ Video +dnet:publication_resource @=@ 0024 @=@ film +dnet:publication_resource @=@ 0024 @=@ http://purl.org/coar/resource_type/c_12ce +dnet:publication_resource @=@ 0024 @=@ http://purl.org/coar/resource_type/c_8a7e +dnet:publication_resource @=@ 0025 @=@ Diagram +dnet:publication_resource @=@ 0025 @=@ Drawing +dnet:publication_resource @=@ 0025 @=@ Figure +dnet:publication_resource @=@ 0025 @=@ Image/Image +dnet:publication_resource @=@ 0025 @=@ Imagen +dnet:publication_resource @=@ 0025 @=@ Photo +dnet:publication_resource @=@ 0025 @=@ Plot +dnet:publication_resource @=@ 0025 @=@ fotó +dnet:publication_resource @=@ 0025 @=@ grafika +dnet:publication_resource @=@ 0025 @=@ http://purl.org/coar/resource_type/c_ecc8 +dnet:publication_resource @=@ 0025 @=@ image +dnet:publication_resource @=@ 0025 @=@ image-diagram +dnet:publication_resource @=@ 0025 @=@ image-drawing +dnet:publication_resource @=@ 0025 @=@ image-figure +dnet:publication_resource @=@ 0025 @=@ image-other +dnet:publication_resource @=@ 0025 @=@ image-photo +dnet:publication_resource @=@ 0025 @=@ image-plot +dnet:publication_resource @=@ 0026 @=@ http://purl.org/coar/resource_type/c_e9a0 +dnet:publication_resource @=@ 0026 @=@ interactiveResource +dnet:publication_resource @=@ 0011 @=@ Internal note +dnet:publication_resource @=@ 0011 @=@ http://purl.org/coar/resource_type/c_18ww +dnet:publication_resource @=@ 0043 @=@ http://purl.org/coar/resource_type/c_0640 +dnet:publication_resource @=@ 0010 @=@ Inaugural lecture +dnet:publication_resource @=@ 0010 @=@ Material didáctico +dnet:publication_resource @=@ 0010 @=@ Public-Lecture +dnet:publication_resource @=@ 0010 @=@ http://purl.org/coar/resource_type/c_8544 +dnet:publication_resource @=@ 0010 @=@ info:eu-repo/semantics/lecture +dnet:publication_resource @=@ 0010 @=@ lesson +dnet:publication_resource @=@ 0010 @=@ Учебный материал +dnet:publication_resource @=@ 0007 @=@ Diploma Project +dnet:publication_resource @=@ 0007 @=@ MSc Thesis +dnet:publication_resource @=@ 0007 @=@ Master Degree +dnet:publication_resource @=@ 0007 @=@ Master's +dnet:publication_resource @=@ 0007 @=@ Masterarbeit u.a. +dnet:publication_resource @=@ 0007 @=@ Masters (Taught) +dnet:publication_resource @=@ 0007 @=@ Masters thesis +dnet:publication_resource @=@ 0007 @=@ Masters-Thesis.Magister +dnet:publication_resource @=@ 0007 @=@ Tesina +dnet:publication_resource @=@ 0007 @=@ Thesis.Master +dnet:publication_resource @=@ 0007 @=@ Trabajo fin de Máster +dnet:publication_resource @=@ 0007 @=@ doc-type:masterThesis +dnet:publication_resource @=@ 0007 @=@ hdr +dnet:publication_resource @=@ 0007 @=@ http://purl.org/coar/resource_type/c_bdcc +dnet:publication_resource @=@ 0007 @=@ info:eu-repo/semantics/masterThesis +dnet:publication_resource @=@ 0007 @=@ masterThesis +dnet:publication_resource @=@ 0007 @=@ memoire +dnet:publication_resource @=@ 0027 @=@ Model/Model +dnet:publication_resource @=@ 0027 @=@ model +dnet:publication_resource @=@ 0020 @=@ Exhibition +dnet:publication_resource @=@ 0020 @=@ Learning Object +dnet:publication_resource @=@ 0020 @=@ Mapa +dnet:publication_resource @=@ 0020 @=@ Modelo de utilidad +dnet:publication_resource @=@ 0020 @=@ PEDAGOGICAL_DOCUMENT +dnet:publication_resource @=@ 0020 @=@ Partitura +dnet:publication_resource @=@ 0020 @=@ Sitio web +dnet:publication_resource @=@ 0020 @=@ Trabajo de divulgación +dnet:publication_resource @=@ 0020 @=@ Web publication/site +dnet:publication_resource @=@ 0020 @=@ application +dnet:publication_resource @=@ 0020 @=@ artefact +dnet:publication_resource @=@ 0020 @=@ carte +dnet:publication_resource @=@ 0020 @=@ composition +dnet:publication_resource @=@ 0020 @=@ document_audiovisuel +dnet:publication_resource @=@ 0020 @=@ http://purl.org/coar/resource_type/c_12cc +dnet:publication_resource @=@ 0020 @=@ http://purl.org/coar/resource_type/c_12cd +dnet:publication_resource @=@ 0020 @=@ http://purl.org/coar/resource_type/c_1843 +dnet:publication_resource @=@ 0020 @=@ http://purl.org/coar/resource_type/c_18cd +dnet:publication_resource @=@ 0020 @=@ http://purl.org/coar/resource_type/c_18cw +dnet:publication_resource @=@ 0020 @=@ http://purl.org/coar/resource_type/c_26e4 +dnet:publication_resource @=@ 0020 @=@ http://purl.org/coar/resource_type/c_7ad9 +dnet:publication_resource @=@ 0020 @=@ http://purl.org/coar/resource_type/c_e059 +dnet:publication_resource @=@ 0020 @=@ info:eu-repo/semantics/other +dnet:publication_resource @=@ 0020 @=@ learningObject +dnet:publication_resource @=@ 0020 @=@ map +dnet:publication_resource @=@ 0020 @=@ misc +dnet:publication_resource @=@ 0020 @=@ other +dnet:publication_resource @=@ 0020 @=@ revue +dnet:publication_resource @=@ 0038 @=@ Abstract +dnet:publication_resource @=@ 0038 @=@ Blog +dnet:publication_resource @=@ 0038 @=@ Book Prospectus +dnet:publication_resource @=@ 0038 @=@ Dictionary Entry +dnet:publication_resource @=@ 0038 @=@ Disclosure +dnet:publication_resource @=@ 0038 @=@ Editorial +dnet:publication_resource @=@ 0038 @=@ Editorial ISI/JCR +dnet:publication_resource @=@ 0038 @=@ Editors +dnet:publication_resource @=@ 0038 @=@ Editors (non peer-reviewed) +dnet:publication_resource @=@ 0038 @=@ Editors (peer-reviewed) +dnet:publication_resource @=@ 0038 @=@ Encyclopedia Entry +dnet:publication_resource @=@ 0038 @=@ Entrada de blog +dnet:publication_resource @=@ 0038 @=@ Funding Submission +dnet:publication_resource @=@ 0038 @=@ HabilitationThesis +dnet:publication_resource @=@ 0038 @=@ License +dnet:publication_resource @=@ 0038 @=@ Manual +dnet:publication_resource @=@ 0038 @=@ Manuscript +dnet:publication_resource @=@ 0038 @=@ Manuscrito +dnet:publication_resource @=@ 0038 @=@ Other publication (non peer-review) +dnet:publication_resource @=@ 0038 @=@ Other publication (peer-review) +dnet:publication_resource @=@ 0038 @=@ Revista +dnet:publication_resource @=@ 0038 @=@ Supervised Student Publication +dnet:publication_resource @=@ 0038 @=@ Tesis/trabajos de grado – Thesis +dnet:publication_resource @=@ 0038 @=@ Text +dnet:publication_resource @=@ 0038 @=@ Text/Text +dnet:publication_resource @=@ 0038 @=@ Trademark +dnet:publication_resource @=@ 0038 @=@ Translation +dnet:publication_resource @=@ 0038 @=@ afterword +dnet:publication_resource @=@ 0038 @=@ avantpropos +dnet:publication_resource @=@ 0038 @=@ bibliography +dnet:publication_resource @=@ 0038 @=@ chronique +dnet:publication_resource @=@ 0038 @=@ compte rendu +dnet:publication_resource @=@ 0038 @=@ correction +dnet:publication_resource @=@ 0038 @=@ foreword +dnet:publication_resource @=@ 0038 @=@ habilitation à diriger des recherches +dnet:publication_resource @=@ 0038 @=@ historicalDocument +dnet:publication_resource @=@ 0038 @=@ http://purl.org/coar/resource_type/c_0040 +dnet:publication_resource @=@ 0038 @=@ http://purl.org/coar/resource_type/c_0857 +dnet:publication_resource @=@ 0038 @=@ http://purl.org/coar/resource_type/c_18cf +dnet:publication_resource @=@ 0038 @=@ http://purl.org/coar/resource_type/c_18wz +dnet:publication_resource @=@ 0038 @=@ http://purl.org/coar/resource_type/c_3e5a +dnet:publication_resource @=@ 0038 @=@ http://purl.org/coar/resource_type/c_46ec +dnet:publication_resource @=@ 0038 @=@ http://purl.org/coar/resource_type/c_6947 +dnet:publication_resource @=@ 0038 @=@ http://purl.org/coar/resource_type/c_7acd +dnet:publication_resource @=@ 0038 @=@ http://purl.org/coar/resource_type/c_86bc +dnet:publication_resource @=@ 0038 @=@ http://purl.org/coar/resource_type/c_b239 +dnet:publication_resource @=@ 0038 @=@ note de lecture +dnet:publication_resource @=@ 0038 @=@ notedelecture +dnet:publication_resource @=@ 0038 @=@ other publication +dnet:publication_resource @=@ 0038 @=@ postface +dnet:publication_resource @=@ 0038 @=@ publication-other +dnet:publication_resource @=@ 0038 @=@ revuedepresse +dnet:publication_resource @=@ 0038 @=@ sa_component +dnet:publication_resource @=@ 0038 @=@ standard +dnet:publication_resource @=@ 0038 @=@ standard-series +dnet:publication_resource @=@ 0013 @=@ A3 Kirjan tai muun kokoomateoksen osa +dnet:publication_resource @=@ 0013 @=@ Book Part (author) +dnet:publication_resource @=@ 0013 @=@ Book Section / Chapter +dnet:publication_resource @=@ 0013 @=@ Book chapter or Essay in book +dnet:publication_resource @=@ 0013 @=@ Book editorial +dnet:publication_resource @=@ 0013 @=@ Book section +dnet:publication_resource @=@ 0013 @=@ Book_Chapter +dnet:publication_resource @=@ 0013 @=@ Buchbeitrag +dnet:publication_resource @=@ 0013 @=@ Capítulo de libro +dnet:publication_resource @=@ 0013 @=@ Contribution to International Book/Monograph +dnet:publication_resource @=@ 0013 @=@ Contribution to International Book/Monograph ISI/JCR +dnet:publication_resource @=@ 0013 @=@ Contribution to National Book/Monograph +dnet:publication_resource @=@ 0013 @=@ Contribution to book (non peer-reviewed) +dnet:publication_resource @=@ 0013 @=@ Contribution to book (peer-reviewed) +dnet:publication_resource @=@ 0013 @=@ Part of book - chapter +dnet:publication_resource @=@ 0013 @=@ book chapter +dnet:publication_resource @=@ 0013 @=@ book-part +dnet:publication_resource @=@ 0013 @=@ bookPart +dnet:publication_resource @=@ 0013 @=@ book_content +dnet:publication_resource @=@ 0013 @=@ chapitre_ouvrage +dnet:publication_resource @=@ 0013 @=@ chapter +dnet:publication_resource @=@ 0013 @=@ doc-type:bookPart +dnet:publication_resource @=@ 0013 @=@ http://purl.org/coar/resource_type/c_3248 +dnet:publication_resource @=@ 0013 @=@ info:eu-repo/semantics/bookPart +dnet:publication_resource @=@ 0013 @=@ publication-section +dnet:publication_resource @=@ 0013 @=@ reference-entry +dnet:publication_resource @=@ 0013 @=@ reference_entry +dnet:publication_resource @=@ 0013 @=@ scientific book chapter +dnet:publication_resource @=@ 0013 @=@ Глава монографии +dnet:publication_resource @=@ 0019 @=@ H1 Myönnetty patentti +dnet:publication_resource @=@ 0019 @=@ Patent +dnet:publication_resource @=@ 0019 @=@ Patente +dnet:publication_resource @=@ 0019 @=@ Solicitud de patente +dnet:publication_resource @=@ 0019 @=@ Traducción de patente +dnet:publication_resource @=@ 0019 @=@ brevet +dnet:publication_resource @=@ 0019 @=@ http://purl.org/coar/resource_type/c_15cd +dnet:publication_resource @=@ 0019 @=@ info:eu-repo/semantics/patent +dnet:publication_resource @=@ 0019 @=@ publication-patent +dnet:publication_resource @=@ 0028 @=@ Service +dnet:publication_resource @=@ 0028 @=@ physicalObject +dnet:publication_resource @=@ 0016 @=@ Pre Print +dnet:publication_resource @=@ 0016 @=@ Pre-print +dnet:publication_resource @=@ 0016 @=@ http://purl.org/coar/resource_type/c_816b +dnet:publication_resource @=@ 0016 @=@ info:eu-repo/semantics/preprint +dnet:publication_resource @=@ 0016 @=@ publication-preprint +dnet:publication_resource @=@ 0016 @=@ Препринт +dnet:publication_resource @=@ 0034 @=@ Project deliverable +dnet:publication_resource @=@ 0034 @=@ http://purl.org/coar/resource_type/c_18op +dnet:publication_resource @=@ 0034 @=@ publication-deliverable +dnet:publication_resource @=@ 0035 @=@ Project milestone +dnet:publication_resource @=@ 0035 @=@ publication-milestone +dnet:publication_resource @=@ 0036 @=@ Proposal +dnet:publication_resource @=@ 0036 @=@ http://purl.org/coar/resource_type/c_baaf +dnet:publication_resource @=@ 0036 @=@ research-proposal +dnet:publication_resource @=@ 0017 @=@ ACTIVITY_REPORT +dnet:publication_resource @=@ 0017 @=@ Commissioned report +dnet:publication_resource @=@ 0017 @=@ D4 Julkaistu kehittämis- tai tutkimusraportti tai -selvitys +dnet:publication_resource @=@ 0017 @=@ Deliverable +dnet:publication_resource @=@ 0017 @=@ Documento tecnico +dnet:publication_resource @=@ 0017 @=@ Project Report +dnet:publication_resource @=@ 0017 @=@ Software documentation +dnet:publication_resource @=@ 0017 @=@ brief-report +dnet:publication_resource @=@ 0017 @=@ case-report +dnet:publication_resource @=@ 0017 @=@ chapitre_rapport +dnet:publication_resource @=@ 0017 @=@ doc-type:report +dnet:publication_resource @=@ 0017 @=@ document_institutionnel +dnet:publication_resource @=@ 0017 @=@ document_technique +dnet:publication_resource @=@ 0017 @=@ http://purl.org/coar/resource_type/c_186u +dnet:publication_resource @=@ 0017 @=@ http://purl.org/coar/resource_type/c_18hj +dnet:publication_resource @=@ 0017 @=@ http://purl.org/coar/resource_type/c_18wq +dnet:publication_resource @=@ 0017 @=@ http://purl.org/coar/resource_type/c_18ws +dnet:publication_resource @=@ 0017 @=@ http://purl.org/coar/resource_type/c_71bd +dnet:publication_resource @=@ 0017 @=@ http://purl.org/coar/resource_type/c_93fc +dnet:publication_resource @=@ 0017 @=@ http://purl.org/coar/resource_type/c_ba1f +dnet:publication_resource @=@ 0017 @=@ info:eu-repo/semantics/report +dnet:publication_resource @=@ 0017 @=@ publication-report +dnet:publication_resource @=@ 0017 @=@ publication-softwaredocumentation +dnet:publication_resource @=@ 0017 @=@ rapport_expertise +dnet:publication_resource @=@ 0017 @=@ rapport_mission +dnet:publication_resource @=@ 0017 @=@ report +dnet:publication_resource @=@ 0017 @=@ report-paper +dnet:publication_resource @=@ 0017 @=@ report-paper_title +dnet:publication_resource @=@ 0017 @=@ report-series +dnet:publication_resource @=@ 0017 @=@ support_cours +dnet:publication_resource @=@ 0014 @=@ Arbeitspapier +dnet:publication_resource @=@ 0014 @=@ Departmental Bulletin Paper +dnet:publication_resource @=@ 0014 @=@ Documento de trabajo +dnet:publication_resource @=@ 0014 @=@ Paper +dnet:publication_resource @=@ 0014 @=@ Project description +dnet:publication_resource @=@ 0014 @=@ Research-Paper +dnet:publication_resource @=@ 0014 @=@ ResearchPaper +dnet:publication_resource @=@ 0014 @=@ Working / discussion paper +dnet:publication_resource @=@ 0014 @=@ Working Paper +dnet:publication_resource @=@ 0014 @=@ Working Paper / Technical Report +dnet:publication_resource @=@ 0014 @=@ doc-type:workingPaper +dnet:publication_resource @=@ 0014 @=@ http://purl.org/coar/resource_type/c_8042 +dnet:publication_resource @=@ 0014 @=@ info:eu-repo/semantics/paper +dnet:publication_resource @=@ 0014 @=@ info:eu-repo/semantics/workingPaper +dnet:publication_resource @=@ 0014 @=@ publication-workingpaper +dnet:publication_resource @=@ 0014 @=@ workingPaper +dnet:publication_resource @=@ 0015 @=@ A2 Katsausartikkeli tieteellisessä aikakauslehdessä +dnet:publication_resource @=@ 0015 @=@ Book Review +dnet:publication_resource @=@ 0015 @=@ Book/Film/Article review +dnet:publication_resource @=@ 0015 @=@ Literature review +dnet:publication_resource @=@ 0015 @=@ Peer review +dnet:publication_resource @=@ 0015 @=@ Reseña bibliográfica +dnet:publication_resource @=@ 0015 @=@ Review Article +dnet:publication_resource @=@ 0015 @=@ RezensionReview +dnet:publication_resource @=@ 0015 @=@ book-review +dnet:publication_resource @=@ 0015 @=@ http://purl.org/coar/resource_type/c_ba08 +dnet:publication_resource @=@ 0015 @=@ http://purl.org/coar/resource_type/c_dcae04bc +dnet:publication_resource @=@ 0015 @=@ http://purl.org/coar/resource_type/c_efa0 +dnet:publication_resource @=@ 0015 @=@ info:eu-repo/semantics/review +dnet:publication_resource @=@ 0015 @=@ peer-review +dnet:publication_resource @=@ 0029 @=@ Software +dnet:publication_resource @=@ 0029 @=@ Software/Software +dnet:publication_resource @=@ 0029 @=@ Workflow +dnet:publication_resource @=@ 0029 @=@ Workflow/Workflow +dnet:publication_resource @=@ 0029 @=@ http://purl.org/coar/resource_type/c_393c +dnet:publication_resource @=@ 0029 @=@ http://purl.org/coar/resource_type/c_5ce6 +dnet:publication_resource @=@ 0029 @=@ http://purl.org/coar/resource_type/c_c950 +dnet:publication_resource @=@ 0032 @=@ http://purl.org/coar/resource_type/c_7bab +dnet:publication_resource @=@ 0030 @=@ http://purl.org/coar/resource_type/c_18cc +dnet:publication_resource @=@ 0030 @=@ sound +dnet:publication_resource @=@ 0044 @=@ Graduate diploma +dnet:publication_resource @=@ 0044 @=@ Undergraduate diploma +dnet:publication_resource @=@ 0000 @=@ UNKNOWN +dnet:publication_resource @=@ 0042 @=@ EGI Virtual Appliance +dnet:languages @=@ abk @=@ ab +dnet:languages @=@ aar @=@ aa +dnet:languages @=@ afr @=@ af +dnet:languages @=@ alb/sqi @=@ sq +dnet:languages @=@ amh @=@ am +dnet:languages @=@ ara @=@ ar +dnet:languages @=@ arm/hye @=@ hy +dnet:languages @=@ asm @=@ as +dnet:languages @=@ ina @=@ ia +dnet:languages @=@ aym @=@ ay +dnet:languages @=@ aze @=@ az +dnet:languages @=@ bak @=@ ba +dnet:languages @=@ baq/eus @=@ eu +dnet:languages @=@ bel @=@ be +dnet:languages @=@ ben @=@ bn +dnet:languages @=@ bih @=@ bh +dnet:languages @=@ bis @=@ bi +dnet:languages @=@ bre @=@ br +dnet:languages @=@ bul @=@ bg +dnet:languages @=@ bur/mya @=@ my +dnet:languages @=@ cat @=@ ca +dnet:languages @=@ chi/zho @=@ zh +dnet:languages @=@ cos @=@ co +dnet:languages @=@ hrv @=@ hr +dnet:languages @=@ hrv @=@ hr +dnet:languages @=@ hrv @=@ scr/hrv +dnet:languages @=@ ces/cze @=@ cs +dnet:languages @=@ dan @=@ da +dnet:languages @=@ dut/nld @=@ dut/nla +dnet:languages @=@ dut/nld @=@ dutdut +dnet:languages @=@ dut/nld @=@ nl +dnet:languages @=@ dut/nld @=@ nl_be +dnet:languages @=@ dut/nld @=@ nl_nl +dnet:languages @=@ dut/nld @=@ nld +dnet:languages @=@ dzo @=@ dz +dnet:languages @=@ eng @=@ en +dnet:languages @=@ eng @=@ en_au +dnet:languages @=@ eng @=@ en_en +dnet:languages @=@ eng @=@ en_gb +dnet:languages @=@ eng @=@ en_nz +dnet:languages @=@ eng @=@ en_us +dnet:languages @=@ eng @=@ english +dnet:languages @=@ eng @=@ en-us +dnet:languages @=@ eng @=@ en-US +dnet:languages @=@ eng @=@ English +dnet:languages @=@ eng @=@ EN +dnet:languages @=@ eng @=@ en angielski +dnet:languages @=@ eng @=@ en-GB +dnet:languages @=@ eng @=@ Englisch +dnet:languages @=@ epo @=@ eo +dnet:languages @=@ est @=@ et +dnet:languages @=@ fao @=@ fo +dnet:languages @=@ fij @=@ fj +dnet:languages @=@ fin @=@ fi +dnet:languages @=@ fin @=@ Finnish +dnet:languages @=@ fra/fre @=@ fr +dnet:languages @=@ fra/fre @=@ FR +dnet:languages @=@ fra/fre @=@ fr_be +dnet:languages @=@ fra/fre @=@ fr_fr +dnet:languages @=@ fra/fre @=@ fre/fra +dnet:languages @=@ fra/fre @=@ fra +dnet:languages @=@ fry @=@ fy +dnet:languages @=@ glg @=@ gl +dnet:languages @=@ geo/kat @=@ ka +dnet:languages @=@ deu/ger @=@ de +dnet:languages @=@ deu/ger @=@ ger/deu +dnet:languages @=@ deu/ger @=@ german +dnet:languages @=@ deu/ger @=@ ger +dnet:languages @=@ deu/ger @=@ deu +dnet:languages @=@ deu/ger @=@ DE-de +dnet:languages @=@ ell/gre @=@ el +dnet:languages @=@ ell/gre @=@ gr +dnet:languages @=@ ell/gre @=@ el-GR +dnet:languages @=@ kal @=@ kl +dnet:languages @=@ grn @=@ gn +dnet:languages @=@ guj @=@ gu +dnet:languages @=@ hau @=@ ha +dnet:languages @=@ heb @=@ he +dnet:languages @=@ hin @=@ hi +dnet:languages @=@ hun @=@ hu +dnet:languages @=@ ice/isl @=@ is +dnet:languages @=@ ine @=@ - +dnet:languages @=@ ind @=@ id +dnet:languages @=@ iku @=@ iu +dnet:languages @=@ ipk @=@ ik +dnet:languages @=@ gai/iri @=@ ga +dnet:languages @=@ gai/iri @=@ gle +dnet:languages @=@ ita @=@ it +dnet:languages @=@ jpn @=@ ja +dnet:languages @=@ jav @=@ jv +dnet:languages @=@ jav @=@ jv/jw +dnet:languages @=@ jav @=@ jw +dnet:languages @=@ kan @=@ kn +dnet:languages @=@ kas @=@ ks +dnet:languages @=@ kaz @=@ kk +dnet:languages @=@ khm @=@ km +dnet:languages @=@ kin @=@ rw +dnet:languages @=@ kir @=@ ky +dnet:languages @=@ kor @=@ ko +dnet:languages @=@ kur @=@ ku +dnet:languages @=@ lao @=@ lo +dnet:languages @=@ lat @=@ la +dnet:languages @=@ lav @=@ lv +dnet:languages @=@ lin @=@ ln +dnet:languages @=@ lit @=@ lt +dnet:languages @=@ mac/mak @=@ mk +dnet:languages @=@ mlg @=@ mg +dnet:languages @=@ may/msa @=@ ms +dnet:languages @=@ mlt @=@ ml +dnet:languages @=@ mao/mri @=@ mi +dnet:languages @=@ mar @=@ mr +dnet:languages @=@ mol @=@ mo +dnet:languages @=@ mon @=@ mn +dnet:languages @=@ nau @=@ na +dnet:languages @=@ nep @=@ ne +dnet:languages @=@ nor @=@ no +dnet:languages @=@ oci @=@ oc +dnet:languages @=@ ori @=@ or +dnet:languages @=@ orm @=@ om +dnet:languages @=@ pan @=@ pa +dnet:languages @=@ fas/per @=@ fa +dnet:languages @=@ pol @=@ pl +dnet:languages @=@ por @=@ pt +dnet:languages @=@ por @=@ pt_pt +dnet:languages @=@ pus @=@ ps +dnet:languages @=@ que @=@ qu +dnet:languages @=@ roh @=@ rm +dnet:languages @=@ ron/rum @=@ ro +dnet:languages @=@ run @=@ rn +dnet:languages @=@ rus @=@ ru +dnet:languages @=@ smo @=@ sm +dnet:languages @=@ sag @=@ sg +dnet:languages @=@ san @=@ sa +dnet:languages @=@ srp @=@ scc/srp +dnet:languages @=@ srp @=@ sr +dnet:languages @=@ scr @=@ sh +dnet:languages @=@ sna @=@ sn +dnet:languages @=@ snd @=@ sd +dnet:languages @=@ sin @=@ si +dnet:languages @=@ sit @=@ - +dnet:languages @=@ slk/slo @=@ sk +dnet:languages @=@ slv @=@ sl +dnet:languages @=@ som @=@ so +dnet:languages @=@ sot @=@ st +dnet:languages @=@ esl/spa @=@ es +dnet:languages @=@ sun @=@ su +dnet:languages @=@ swa @=@ sw +dnet:languages @=@ ssw @=@ ss +dnet:languages @=@ swe @=@ sv +dnet:languages @=@ swe @=@ sve/swe +dnet:languages @=@ tgl @=@ tl +dnet:languages @=@ tgk @=@ tg +dnet:languages @=@ tam @=@ ta +dnet:languages @=@ tat @=@ tt +dnet:languages @=@ tel @=@ te +dnet:languages @=@ tha @=@ th +dnet:languages @=@ tha @=@ thai +dnet:languages @=@ bod/tib @=@ bo +dnet:languages @=@ tir @=@ ti +dnet:languages @=@ tog @=@ to +dnet:languages @=@ tso @=@ ts +dnet:languages @=@ tsn @=@ tn +dnet:languages @=@ tur @=@ tr +dnet:languages @=@ tuk @=@ tk +dnet:languages @=@ twi @=@ tw +dnet:languages @=@ uig @=@ ug +dnet:languages @=@ ukr @=@ uk +dnet:languages @=@ und @=@ UNKNOWN +dnet:languages @=@ und @=@ none +dnet:languages @=@ urd @=@ ur +dnet:languages @=@ uzb @=@ uz +dnet:languages @=@ vie @=@ vi +dnet:languages @=@ vol @=@ vo +dnet:languages @=@ wln @=@ wa +dnet:languages @=@ cym/wel @=@ cy +dnet:languages @=@ wol @=@ wo +dnet:languages @=@ xho @=@ xh +dnet:languages @=@ yid @=@ yi +dnet:languages @=@ yor @=@ yo +dnet:languages @=@ zha @=@ za +dnet:languages @=@ zul @=@ zu +dnet:result_typologies @=@ dataset @=@ 0021 +dnet:result_typologies @=@ dataset @=@ 0024 +dnet:result_typologies @=@ dataset @=@ 0025 +dnet:result_typologies @=@ dataset @=@ 0030 +dnet:result_typologies @=@ dataset @=@ 0033 +dnet:result_typologies @=@ dataset @=@ 0037 +dnet:result_typologies @=@ dataset @=@ 0039 +dnet:result_typologies @=@ dataset @=@ 0046 +dnet:result_typologies @=@ other @=@ 0000 +dnet:result_typologies @=@ other @=@ 0010 +dnet:result_typologies @=@ other @=@ 0018 +dnet:result_typologies @=@ other @=@ 0020 +dnet:result_typologies @=@ other @=@ 0022 +dnet:result_typologies @=@ other @=@ 0023 +dnet:result_typologies @=@ other @=@ 0026 +dnet:result_typologies @=@ other @=@ 0027 +dnet:result_typologies @=@ other @=@ 0028 +dnet:result_typologies @=@ other @=@ 0042 +dnet:result_typologies @=@ publication @=@ 0001 +dnet:result_typologies @=@ publication @=@ 0002 +dnet:result_typologies @=@ publication @=@ 0004 +dnet:result_typologies @=@ publication @=@ 0005 +dnet:result_typologies @=@ publication @=@ 0006 +dnet:result_typologies @=@ publication @=@ 0007 +dnet:result_typologies @=@ publication @=@ 0008 +dnet:result_typologies @=@ publication @=@ 0009 +dnet:result_typologies @=@ publication @=@ 0011 +dnet:result_typologies @=@ publication @=@ 0012 +dnet:result_typologies @=@ publication @=@ 0013 +dnet:result_typologies @=@ publication @=@ 0014 +dnet:result_typologies @=@ publication @=@ 0015 +dnet:result_typologies @=@ publication @=@ 0016 +dnet:result_typologies @=@ publication @=@ 0017 +dnet:result_typologies @=@ publication @=@ 0019 +dnet:result_typologies @=@ publication @=@ 0031 +dnet:result_typologies @=@ publication @=@ 0032 +dnet:result_typologies @=@ publication @=@ 0034 +dnet:result_typologies @=@ publication @=@ 0035 +dnet:result_typologies @=@ publication @=@ 0036 +dnet:result_typologies @=@ publication @=@ 0038 +dnet:result_typologies @=@ publication @=@ 0044 +dnet:result_typologies @=@ publication @=@ 0045 +dnet:result_typologies @=@ software @=@ 0029 +dnet:result_typologies @=@ software @=@ 0040 +dnet:countries @=@ AF @=@ AFG +dnet:countries @=@ AF @=@ Afghanistan +dnet:countries @=@ AD @=@ Andorra +dnet:countries @=@ AO @=@ Angola +dnet:countries @=@ AR @=@ ARG +dnet:countries @=@ AR @=@ Argentina +dnet:countries @=@ AU @=@ AUS +dnet:countries @=@ AU @=@ Australia +dnet:countries @=@ AT @=@ AUT +dnet:countries @=@ AT @=@ Austria +dnet:countries @=@ AZ @=@ AZE +dnet:countries @=@ BD @=@ Bangladesh +dnet:countries @=@ BY @=@ Belarus +dnet:countries @=@ BE @=@ BEL +dnet:countries @=@ BE @=@ Belgium +dnet:countries @=@ BJ @=@ BEN +dnet:countries @=@ BO @=@ Bolivia, Plurinational State of +dnet:countries @=@ BA @=@ BIH +dnet:countries @=@ BA @=@ Bosnia-Hercegovina +dnet:countries @=@ BR @=@ BRA +dnet:countries @=@ BR @=@ Brazil +dnet:countries @=@ BG @=@ Bulgaria +dnet:countries @=@ BF @=@ BFA +dnet:countries @=@ KH @=@ Cambodia +dnet:countries @=@ KH @=@ Cambogia +dnet:countries @=@ KH @=@ Campuchea +dnet:countries @=@ CM @=@ CMR +dnet:countries @=@ CA @=@ CAN +dnet:countries @=@ CA @=@ Canada +dnet:countries @=@ CV @=@ Cape Verde +dnet:countries @=@ CL @=@ CHL +dnet:countries @=@ CL @=@ Chile +dnet:countries @=@ CN @=@ CHN +dnet:countries @=@ CN @=@ China +dnet:countries @=@ CO @=@ COL +dnet:countries @=@ CO @=@ Colombia +dnet:countries @=@ CD @=@ Congo +dnet:countries @=@ CD @=@ Congo Democratic Republic (formerly Zaire) +dnet:countries @=@ CD @=@ Congo, Republic +dnet:countries @=@ CD @=@ Congo, the Democratic Republic of the +dnet:countries @=@ CD @=@ Zaire +dnet:countries @=@ CR @=@ CRI +dnet:countries @=@ CI @=@ CIV +dnet:countries @=@ CI @=@ Ivory Coast +dnet:countries @=@ HR @=@ Croatia +dnet:countries @=@ HR @=@ HRV +dnet:countries @=@ CY @=@ CYP +dnet:countries @=@ CY @=@ Cyprus +dnet:countries @=@ CZ @=@ CZE +dnet:countries @=@ CZ @=@ Czech Republic +dnet:countries @=@ CZ @=@ Czechia +dnet:countries @=@ CZ @=@ Czechoslovakia +dnet:countries @=@ DK @=@ DNK +dnet:countries @=@ DK @=@ Denmark +dnet:countries @=@ EC @=@ Ecuador +dnet:countries @=@ EG @=@ EGY +dnet:countries @=@ EG @=@ Egypt +dnet:countries @=@ SV @=@ SLV +dnet:countries @=@ EE @=@ EST +dnet:countries @=@ EE @=@ Estonia +dnet:countries @=@ ET @=@ ETH +dnet:countries @=@ EU @=@ EEC +dnet:countries @=@ FJ @=@ FJI +dnet:countries @=@ FI @=@ FIN +dnet:countries @=@ FI @=@ Finland +dnet:countries @=@ MK @=@ Macedonia +dnet:countries @=@ MK @=@ Macedonia, the Former Yugoslav Republic Of +dnet:countries @=@ MK @=@ North Macedonia +dnet:countries @=@ FR @=@ FRA +dnet:countries @=@ FR @=@ France +dnet:countries @=@ PF @=@ French Polynesia +dnet:countries @=@ PF @=@ PYF +dnet:countries @=@ TF @=@ French Southern Territories +dnet:countries @=@ GE @=@ Georgia +dnet:countries @=@ DE @=@ DEU +dnet:countries @=@ DE @=@ Germany +dnet:countries @=@ DE @=@ Germany, Berlin +dnet:countries @=@ GH @=@ GHA +dnet:countries @=@ GR @=@ EL +dnet:countries @=@ GR @=@ GRC +dnet:countries @=@ GL @=@ GRL +dnet:countries @=@ GN @=@ Guinea +dnet:countries @=@ GW @=@ Guinea-Bissau +dnet:countries @=@ VA @=@ Vatican State +dnet:countries @=@ HK @=@ HKG +dnet:countries @=@ HK @=@ Hong Kong +dnet:countries @=@ HK @=@ Hongkong +dnet:countries @=@ HU @=@ HUN +dnet:countries @=@ HU @=@ Hungary +dnet:countries @=@ IS @=@ ISL +dnet:countries @=@ IN @=@ IND +dnet:countries @=@ IN @=@ India +dnet:countries @=@ ID @=@ IDN +dnet:countries @=@ ID @=@ Indonesia +dnet:countries @=@ IR @=@ Iran +dnet:countries @=@ IR @=@ Iran, Islamic Republic of +dnet:countries @=@ IE @=@ IRL +dnet:countries @=@ IE @=@ Ireland +dnet:countries @=@ IL @=@ ISR +dnet:countries @=@ IL @=@ Israel +dnet:countries @=@ IT @=@ ITA +dnet:countries @=@ IT @=@ Italy +dnet:countries @=@ JM @=@ Jamaica +dnet:countries @=@ JP @=@ JPN +dnet:countries @=@ JP @=@ Japan +dnet:countries @=@ KZ @=@ KAZ +dnet:countries @=@ KZ @=@ Kazakistan +dnet:countries @=@ KZ @=@ Kazakstan +dnet:countries @=@ KE @=@ KEN +dnet:countries @=@ KE @=@ Kenya +dnet:countries @=@ KR @=@ KOR +dnet:countries @=@ KR @=@ Korea, Republic of +dnet:countries @=@ KR @=@ Korean Republic (South Korea) +dnet:countries @=@ KP @=@ PRK +dnet:countries @=@ LV @=@ LVA +dnet:countries @=@ LY @=@ Libya +dnet:countries @=@ LT @=@ LTU +dnet:countries @=@ LU @=@ LUX +dnet:countries @=@ LU @=@ Luxembourg +dnet:countries @=@ MO @=@ Macao +dnet:countries @=@ MG @=@ Madagascar +dnet:countries @=@ MY @=@ Malaysia +dnet:countries @=@ ML @=@ Mali +dnet:countries @=@ MT @=@ Malta +dnet:countries @=@ MU @=@ Mauritius +dnet:countries @=@ MX @=@ MEX +dnet:countries @=@ MX @=@ Mexico +dnet:countries @=@ FM @=@ Micronesia +dnet:countries @=@ MD @=@ Moldova +dnet:countries @=@ MD @=@ Moldova, Republic of +dnet:countries @=@ MN @=@ Mongolia +dnet:countries @=@ MA @=@ Morocco +dnet:countries @=@ MZ @=@ Mozambique +dnet:countries @=@ NA @=@ NAM +dnet:countries @=@ NL @=@ NLD +dnet:countries @=@ NL @=@ Netherlands +dnet:countries @=@ AN @=@ Netherlands Antilles +dnet:countries @=@ NC @=@ NCL +dnet:countries @=@ NZ @=@ NZL +dnet:countries @=@ NZ @=@ New Zealand +dnet:countries @=@ NO @=@ NOR +dnet:countries @=@ NO @=@ Norway +dnet:countries @=@ OC @=@ Australasia +dnet:countries @=@ OM @=@ Oman +dnet:countries @=@ PK @=@ PAK +dnet:countries @=@ PK @=@ Pakistan +dnet:countries @=@ PS @=@ Palestin, State of +dnet:countries @=@ PS @=@ Palestine, State of +dnet:countries @=@ PS @=@ Palestinian Territory, Occupied +dnet:countries @=@ PA @=@ PAN +dnet:countries @=@ PA @=@ Panama +dnet:countries @=@ PG @=@ PapuaNew Guinea +dnet:countries @=@ PE @=@ PER +dnet:countries @=@ PH @=@ PHL +dnet:countries @=@ PH @=@ Philippines +dnet:countries @=@ PL @=@ POL +dnet:countries @=@ PL @=@ Poland +dnet:countries @=@ PT @=@ PRT +dnet:countries @=@ PT @=@ Portugal +dnet:countries @=@ PR @=@ Puerto Rico +dnet:countries @=@ RO @=@ ROU +dnet:countries @=@ RO @=@ Romania +dnet:countries @=@ RU @=@ RUS +dnet:countries @=@ RU @=@ Russia +dnet:countries @=@ RU @=@ Russian Federation +dnet:countries @=@ RE @=@ Réunion +dnet:countries @=@ KN @=@ Saint Kitts And Nevis +dnet:countries @=@ SA @=@ Saudi Arabia +dnet:countries @=@ SN @=@ SEN +dnet:countries @=@ RS @=@ SRB +dnet:countries @=@ CS @=@ Serbia and Montenegro +dnet:countries @=@ SG @=@ SGP +dnet:countries @=@ SG @=@ Singapore +dnet:countries @=@ SK @=@ SVK +dnet:countries @=@ SI @=@ SVN +dnet:countries @=@ SI @=@ Slovenia +dnet:countries @=@ ZA @=@ South Africa +dnet:countries @=@ ZA @=@ ZAF +dnet:countries @=@ ES @=@ ESP +dnet:countries @=@ ES @=@ Spain +dnet:countries @=@ LK @=@ LKA +dnet:countries @=@ LK @=@ Sri Lanka +dnet:countries @=@ SD @=@ SDN +dnet:countries @=@ SR @=@ Suriname +dnet:countries @=@ SE @=@ SWE +dnet:countries @=@ SE @=@ Sweden +dnet:countries @=@ CH @=@ CHE +dnet:countries @=@ CH @=@ Switzerland +dnet:countries @=@ SY @=@ Syria +dnet:countries @=@ ST @=@ Sao Tome and Principe +dnet:countries @=@ TW @=@ TWN +dnet:countries @=@ TW @=@ Taiwan +dnet:countries @=@ TW @=@ Taiwan, Province of China +dnet:countries @=@ TZ @=@ Tanzania +dnet:countries @=@ TZ @=@ Tanzania, United Republic of +dnet:countries @=@ TH @=@ THA +dnet:countries @=@ TH @=@ Thailand +dnet:countries @=@ TL @=@ East Timor +dnet:countries @=@ TN @=@ TUN +dnet:countries @=@ TN @=@ Tunisia +dnet:countries @=@ TR @=@ TUR +dnet:countries @=@ TR @=@ Turkey +dnet:countries @=@ UNKNOWN @=@ AAA +dnet:countries @=@ UNKNOWN @=@ [Unknown] +dnet:countries @=@ UNKNOWN @=@ _? +dnet:countries @=@ UA @=@ UKR +dnet:countries @=@ UA @=@ Ukraine +dnet:countries @=@ AE @=@ United Arab Emirates +dnet:countries @=@ GB @=@ England +dnet:countries @=@ GB @=@ GBR +dnet:countries @=@ GB @=@ Great Britain +dnet:countries @=@ GB @=@ Great Britain and Northern Ireland +dnet:countries @=@ GB @=@ Scotland +dnet:countries @=@ GB @=@ UK +dnet:countries @=@ GB @=@ United Kingdom +dnet:countries @=@ US @=@ USA +dnet:countries @=@ US @=@ United States +dnet:countries @=@ US @=@ United States of America +dnet:countries @=@ UY @=@ Uruguay +dnet:countries @=@ UZ @=@ Uzbekistan +dnet:countries @=@ VE @=@ Venezuela, Bolivarian Republic of +dnet:countries @=@ VN @=@ Vietnam +dnet:countries @=@ VG @=@ British Virgin Islands +dnet:countries @=@ YU @=@ Jugoslavia +dnet:countries @=@ YU @=@ Yugoslavia +dnet:countries @=@ ZW @=@ ABW +dnet:protocols @=@ oai @=@ OAI-PMH +dnet:protocols @=@ oai @=@ OAI_PMH +dnet:pid_types @=@ orcid @=@ ORCID12 +dnet:pid_types @=@ handle @=@ hdl +dnet:review_levels @=@ 0000 @=@ UNKNOWN +dnet:review_levels @=@ 0002 @=@ 80 大阪経大学会「Working Paper」 +dnet:review_levels @=@ 0002 @=@ AO +dnet:review_levels @=@ 0002 @=@ ARTICLE SANS COMITE DE LECTURE (ASCL) +dnet:review_levels @=@ 0002 @=@ Arbeitspapier +dnet:review_levels @=@ 0002 @=@ Arbeitspapier [workingPaper] +dnet:review_levels @=@ 0002 @=@ Article (author) +dnet:review_levels @=@ 0002 @=@ Article type: preprint +dnet:review_levels @=@ 0002 @=@ Article(author version) +dnet:review_levels @=@ 0002 @=@ Article, not peer-reviewed +dnet:review_levels @=@ 0002 @=@ Articulo no evaluado +dnet:review_levels @=@ 0002 @=@ Artigo Solicitado e Não Avaliado por Pares +dnet:review_levels @=@ 0002 @=@ Artigo não avaliado pelos pares +dnet:review_levels @=@ 0002 @=@ Artigo não avaliado por pares +dnet:review_levels @=@ 0002 @=@ Artigo não avaliado por pres +dnet:review_levels @=@ 0002 @=@ Artikkeli|Artikkeli ammattilehdessä. Ei vertaisarvioitu +dnet:review_levels @=@ 0002 @=@ Artículo no evaluado +dnet:review_levels @=@ 0002 @=@ Book (non peer-reviewed) +dnet:review_levels @=@ 0002 @=@ Book Part (author) +dnet:review_levels @=@ 0002 @=@ Book item; Non-peer-reviewed +dnet:review_levels @=@ 0002 @=@ Conference preprint +dnet:review_levels @=@ 0002 @=@ Contribution to book (non peer-reviewed) +dnet:review_levels @=@ 0002 @=@ Discussion Paper +dnet:review_levels @=@ 0002 @=@ Document de travail (Working Paper) +dnet:review_levels @=@ 0002 @=@ Documento de trabajo +dnet:review_levels @=@ 0002 @=@ Documento de trabajo de investigaci??n +dnet:review_levels @=@ 0002 @=@ Draft +dnet:review_levels @=@ 0002 @=@ E-pub ahead of print +dnet:review_levels @=@ 0002 @=@ Editorial de revista, no evaluado por pares +dnet:review_levels @=@ 0002 @=@ Editorial de revista, não avaliado por pares +dnet:review_levels @=@ 0002 @=@ Editorial não avaliado pelos pares +dnet:review_levels @=@ 0002 @=@ Editors (non peer-reviewed) +dnet:review_levels @=@ 0002 @=@ Epub ahead of print +dnet:review_levels @=@ 0002 @=@ Hakemlik Sürecinden Geçmiş Makale +dnet:review_levels @=@ 0002 @=@ Hakemlik sürecindeki makale +dnet:review_levels @=@ 0002 @=@ Hakemlik sürecinden geçmemiş kitap değerlendirmesi +dnet:review_levels @=@ 0002 @=@ Journal Article (author version) +dnet:review_levels @=@ 0002 @=@ Journal Article Preprint +dnet:review_levels @=@ 0002 @=@ Journal Editorial, not peer-reviewed +dnet:review_levels @=@ 0002 @=@ Journal article; Non-peer-reviewed +dnet:review_levels @=@ 0002 @=@ Journal:WorkingPaper +dnet:review_levels @=@ 0002 @=@ Manuscript (preprint) +dnet:review_levels @=@ 0002 @=@ Monográfico (Informes, Documentos de trabajo, etc.) +dnet:review_levels @=@ 0002 @=@ NOTE INTERNE OU DE TRAVAIL +dnet:review_levels @=@ 0002 @=@ Nicht begutachteter Beitrag +dnet:review_levels @=@ 0002 @=@ No evaluado por pares +dnet:review_levels @=@ 0002 @=@ Non-Refereed +dnet:review_levels @=@ 0002 @=@ Non-refeered article +dnet:review_levels @=@ 0002 @=@ Non-refereed Article +dnet:review_levels @=@ 0002 @=@ Non-refereed Book Review +dnet:review_levels @=@ 0002 @=@ Non-refereed Review +dnet:review_levels @=@ 0002 @=@ Non-refereed Text +dnet:review_levels @=@ 0002 @=@ NonPeerReviewed +dnet:review_levels @=@ 0002 @=@ Not Peer reviewed +dnet:review_levels @=@ 0002 @=@ Not Reviewed +dnet:review_levels @=@ 0002 @=@ Not peer-reviewed +dnet:review_levels @=@ 0002 @=@ Não Avaliado por Pares +dnet:review_levels @=@ 0002 @=@ Não avaliada pelos pares +dnet:review_levels @=@ 0002 @=@ Não avaliado pelos pares +dnet:review_levels @=@ 0002 @=@ Original article (non peer-reviewed) +dnet:review_levels @=@ 0002 @=@ Other publication (non peer-review) +dnet:review_levels @=@ 0002 @=@ Pre Print +dnet:review_levels @=@ 0002 @=@ Pre-print +dnet:review_levels @=@ 0002 @=@ Preprint Article +dnet:review_levels @=@ 0002 @=@ Preprints +dnet:review_levels @=@ 0002 @=@ Preprints, Working Papers, ... +dnet:review_levels @=@ 0002 @=@ Rapporto tecnico / Working Paper / Rapporto di progetto +dnet:review_levels @=@ 0002 @=@ Resumo Não Avaliado por Pares +dnet:review_levels @=@ 0002 @=@ Review article (non peer-reviewed) +dnet:review_levels @=@ 0002 @=@ SMUR +dnet:review_levels @=@ 0002 @=@ Submissão dos artigos +dnet:review_levels @=@ 0002 @=@ Submitted version +dnet:review_levels @=@ 0002 @=@ Vertaisarvioimaton kirjan tai muun kokoomateoksen osa +dnet:review_levels @=@ 0002 @=@ Vorabdruck +dnet:review_levels @=@ 0002 @=@ Wetensch. publ. non-refereed +dnet:review_levels @=@ 0002 @=@ Working / discussion paper +dnet:review_levels @=@ 0002 @=@ Working Document +dnet:review_levels @=@ 0002 @=@ Working Notes +dnet:review_levels @=@ 0002 @=@ Working Paper +dnet:review_levels @=@ 0002 @=@ Working Paper / Technical Report +dnet:review_levels @=@ 0002 @=@ Working Papers +dnet:review_levels @=@ 0002 @=@ WorkingPaper +dnet:review_levels @=@ 0002 @=@ article in non peer-reviewed journal +dnet:review_levels @=@ 0002 @=@ articolo preliminare +dnet:review_levels @=@ 0002 @=@ articulo preliminar +dnet:review_levels @=@ 0002 @=@ articulo sin revision por pares +dnet:review_levels @=@ 0002 @=@ artigo preliminar +dnet:review_levels @=@ 0002 @=@ artigo sem revisão +dnet:review_levels @=@ 0002 @=@ artículo preliminar +dnet:review_levels @=@ 0002 @=@ artículo sin revisión por pares +dnet:review_levels @=@ 0002 @=@ bookchapter (author version) +dnet:review_levels @=@ 0002 @=@ borrador +dnet:review_levels @=@ 0002 @=@ column (author version) +dnet:review_levels @=@ 0002 @=@ communication_invitee +dnet:review_levels @=@ 0002 @=@ doc-type:preprint +dnet:review_levels @=@ 0002 @=@ doc-type:workingPaper +dnet:review_levels @=@ 0002 @=@ draf +dnet:review_levels @=@ 0002 @=@ eu-repo/semantics/submittedVersion +dnet:review_levels @=@ 0002 @=@ http://purl.org/coar/resource_type/c_8042 +dnet:review_levels @=@ 0002 @=@ http://purl.org/coar/resource_type/c_816b +dnet:review_levels @=@ 0002 @=@ http://purl.org/coar/version/c_71e4c1898caa6e32 +dnet:review_levels @=@ 0002 @=@ http://purl.org/coar/version/c_b1a7d7d4d402bcce +dnet:review_levels @=@ 0002 @=@ http://purl.org/eprint/type/SubmittedBookItem +dnet:review_levels @=@ 0002 @=@ http://purl.org/eprint/type/SubmittedJournalArticle +dnet:review_levels @=@ 0002 @=@ http://purl.org/info:eu-repo/semantics/authorVersion +dnet:review_levels @=@ 0002 @=@ http://purl.org/info:eu-repo/semantics/submittedVersion +dnet:review_levels @=@ 0002 @=@ http://purl.org/spar/fabio/Preprint +dnet:review_levels @=@ 0002 @=@ http://purl.org/spar/fabio/WorkingPaper +dnet:review_levels @=@ 0002 @=@ https://dictionary.casrai.org/Preprint +dnet:review_levels @=@ 0002 @=@ info:ar-repo/semantics/documento de trabajo +dnet:review_levels @=@ 0002 @=@ info:ar-repo/semantics/documentoDeTrabajo +dnet:review_levels @=@ 0002 @=@ info:eu repo/semantics/draft +dnet:review_levels @=@ 0002 @=@ info:eu-repo/semantics/authorVersion +dnet:review_levels @=@ 0002 @=@ info:eu-repo/semantics/draft +dnet:review_levels @=@ 0002 @=@ info:eu-repo/semantics/preprint +dnet:review_levels @=@ 0002 @=@ info:eu-repo/semantics/submitedVersion +dnet:review_levels @=@ 0002 @=@ info:eu-repo/semantics/submittedVersion +dnet:review_levels @=@ 0002 @=@ info:eu-repo/semantics/unReviewed +dnet:review_levels @=@ 0002 @=@ info:eu-repo/semantics/updatedVersion +dnet:review_levels @=@ 0002 @=@ info:eu-repo/semantics/workingPaper +dnet:review_levels @=@ 0002 @=@ info:eu-repo/submittedVersion +dnet:review_levels @=@ 0002 @=@ info:ulb-repo/semantics/articleNonPeerReview +dnet:review_levels @=@ 0002 @=@ info:ulb-repo/semantics/openurl/vlink-workingpaper +dnet:review_levels @=@ 0002 @=@ info:ulb-repo/semantics/workingPaper +dnet:review_levels @=@ 0002 @=@ non peer-reviewed article +dnet:review_levels @=@ 0002 @=@ non-refereed review article +dnet:review_levels @=@ 0002 @=@ não avaliado +dnet:review_levels @=@ 0002 @=@ preprint +dnet:review_levels @=@ 0002 @=@ prepublicación +dnet:review_levels @=@ 0002 @=@ proceeding, seminar, workshop without peer review +dnet:review_levels @=@ 0002 @=@ proceedings (author version) +dnet:review_levels @=@ 0002 @=@ pré-print +dnet:review_levels @=@ 0002 @=@ pré-publication +dnet:review_levels @=@ 0002 @=@ préprint +dnet:review_levels @=@ 0002 @=@ prépublication +dnet:review_levels @=@ 0002 @=@ publicació preliminar +dnet:review_levels @=@ 0002 @=@ publication-preprint +dnet:review_levels @=@ 0002 @=@ publication-workingpaper +dnet:review_levels @=@ 0002 @=@ submitedVersion +dnet:review_levels @=@ 0002 @=@ submittedVersion +dnet:review_levels @=@ 0002 @=@ voordruk +dnet:review_levels @=@ 0002 @=@ workingPaper +dnet:review_levels @=@ 0002 @=@ ön baskı +dnet:review_levels @=@ 0002 @=@ Препринт +dnet:review_levels @=@ 0002 @=@ предпечатная версия публикации +dnet:review_levels @=@ 0002 @=@ препринт статьи +dnet:review_levels @=@ 0002 @=@ ディスカッション/ワーキング・ペーパー DP/WP +dnet:review_levels @=@ 0002 @=@ プレプリント +dnet:review_levels @=@ 0002 @=@ プレプリント Preprint +dnet:review_levels @=@ 0002 @=@ プレプリント(Preprint) +dnet:review_levels @=@ 0002 @=@ 印刷物/電子媒体-その他(査読無し) +dnet:review_levels @=@ 0002 @=@ 印刷物/電子媒体-テクニカルレポート類(査読無し) +dnet:review_levels @=@ 0002 @=@ 印刷物/電子媒体-会議発表論文(査読無し) +dnet:review_levels @=@ 0002 @=@ 印刷物/電子媒体-図書(査読無し) +dnet:review_levels @=@ 0002 @=@ 印刷物/電子媒体-学術雑誌論文(査読無し) +dnet:review_levels @=@ 0002 @=@ 印刷物/電子媒体-紀要論文(査読無し) +dnet:review_levels @=@ 0002 @=@ 印刷物/電子媒体-雑誌記事(査読無し) +dnet:review_levels @=@ 0002 @=@ 预印本 +dnet:review_levels @=@ 0001 @=@ ##rt.metadata.pkp.peerReviewed## +dnet:review_levels @=@ 0001 @=@ A1 Alkuperäisartikkeli tieteellisessä aikakauslehdessä +dnet:review_levels @=@ 0001 @=@ Art?culo revisado por pares +dnet:review_levels @=@ 0001 @=@ Article revisat per persones expertes +dnet:review_levels @=@ 0001 @=@ Article type: peer review +dnet:review_levels @=@ 0001 @=@ Article évalué par les pairs +dnet:review_levels @=@ 0001 @=@ Article évalué par des pairs +dnet:review_levels @=@ 0001 @=@ Article évalué par les pairs +dnet:review_levels @=@ 0001 @=@ Articolo valutato secondo i criteri della peer review +dnet:review_levels @=@ 0001 @=@ Articulo evaluado por dos pares +dnet:review_levels @=@ 0001 @=@ Articulo revisado por pares +dnet:review_levels @=@ 0001 @=@ Artigo Avaliado pelos Pares +dnet:review_levels @=@ 0001 @=@ Artigo Revisto por Pares +dnet:review_levels @=@ 0001 @=@ Artigo avaliado por blind peer review +dnet:review_levels @=@ 0001 @=@ Artigo avaliado por pares +dnet:review_levels @=@ 0001 @=@ Artigo de convidado. Avaliado pelos pares +dnet:review_levels @=@ 0001 @=@ Artigos; Avaliado pelos pares +dnet:review_levels @=@ 0001 @=@ Artículo de investigación, Investigaciones originales, Artículo evaluado por pares, Investigaciones empíricas +dnet:review_levels @=@ 0001 @=@ Artículo evaluado por pares +dnet:review_levels @=@ 0001 @=@ Artículo evaluado por pares, Ensayos de investigación +dnet:review_levels @=@ 0001 @=@ Artículo evaluado por pares, Investigaciones empíricas, Artículos de investigación +dnet:review_levels @=@ 0001 @=@ Artículo revisado +dnet:review_levels @=@ 0001 @=@ Artículo revisado por pares +dnet:review_levels @=@ 0001 @=@ Artículos de estudiantes, Artículo evaluado por pares, Artículos de investigación +dnet:review_levels @=@ 0001 @=@ Artículos de investigación evaluados por doble ciego +dnet:review_levels @=@ 0001 @=@ Artículos evaluadores por doble ciego +dnet:review_levels @=@ 0001 @=@ Artículos evaluados por pares +dnet:review_levels @=@ 0001 @=@ Artículos evaluados por pares académicos +dnet:review_levels @=@ 0001 @=@ Artículos revisados por pares +dnet:review_levels @=@ 0001 @=@ Avaliadas pelos pares +dnet:review_levels @=@ 0001 @=@ Avaliado anonimamente por pares +dnet:review_levels @=@ 0001 @=@ Avaliado em duplo cego por pares +dnet:review_levels @=@ 0001 @=@ Avaliado pela Editoria +dnet:review_levels @=@ 0001 @=@ Avaliado pela Editoria. Avaliado pelos pares. +dnet:review_levels @=@ 0001 @=@ Avaliado pelo Editoria +dnet:review_levels @=@ 0001 @=@ Avaliado pelo pares +dnet:review_levels @=@ 0001 @=@ Avaliado pelos Editores +dnet:review_levels @=@ 0001 @=@ Avaliado pelos pares +dnet:review_levels @=@ 0001 @=@ Avaliado pelos pares, Artigo de convidado +dnet:review_levels @=@ 0001 @=@ Avaliado pelos pares, Artigos Originais +dnet:review_levels @=@ 0001 @=@ Avaliado pelos pares, Artigos Originais, Artigos de Revisão +dnet:review_levels @=@ 0001 @=@ Avaliado pelos pares. Avaliado pelo Editoria +dnet:review_levels @=@ 0001 @=@ Avaliado po Pares +dnet:review_levels @=@ 0001 @=@ Avaliado por Editor +dnet:review_levels @=@ 0001 @=@ Avaliado por pares +dnet:review_levels @=@ 0001 @=@ Avaliados pelos pares +dnet:review_levels @=@ 0001 @=@ Avaliados por Pares +dnet:review_levels @=@ 0001 @=@ Blind Peer-reviewed Article +dnet:review_levels @=@ 0001 @=@ Book (peer-reviewed) +dnet:review_levels @=@ 0001 @=@ Comentario de libros, Comentario de revistas, Comentario de conferencias, Artículo evaluado por pares, Artículo de investigación +dnet:review_levels @=@ 0001 @=@ Conference paper; Peer-reviewed +dnet:review_levels @=@ 0001 @=@ Contribution to book (peer-reviewed) +dnet:review_levels @=@ 0001 @=@ Documento Avaliado por Pares +dnet:review_levels @=@ 0001 @=@ Double blind evaluation articles +dnet:review_levels @=@ 0001 @=@ Double blind peer review +dnet:review_levels @=@ 0001 @=@ Editors (peer-reviewed) +dnet:review_levels @=@ 0001 @=@ Evaluación por pares +dnet:review_levels @=@ 0001 @=@ Evaluado por pares +dnet:review_levels @=@ 0001 @=@ Evaluados por los pares +dnet:review_levels @=@ 0001 @=@ Hakem sürecinden geçmiş makale +dnet:review_levels @=@ 0001 @=@ Hakemli makale +dnet:review_levels @=@ 0001 @=@ Hakemlik Sürecinden Geçmiş +dnet:review_levels @=@ 0001 @=@ Invited Peer-Reviewed Article +dnet:review_levels @=@ 0001 @=@ Journal article; Peer-reviewed +dnet:review_levels @=@ 0001 @=@ Original article (peer-reviewed) +dnet:review_levels @=@ 0001 @=@ Other publication (peer-review) +dnet:review_levels @=@ 0001 @=@ Paper peer-reviewed +dnet:review_levels @=@ 0001 @=@ Papers evaluated by academic peers +dnet:review_levels @=@ 0001 @=@ Peer reviewed +dnet:review_levels @=@ 0001 @=@ Peer reviewed article +dnet:review_levels @=@ 0001 @=@ Peer reviewed invited commentry +dnet:review_levels @=@ 0001 @=@ Peer-Reviewed Protocol +dnet:review_levels @=@ 0001 @=@ Peer-reviewd Article +dnet:review_levels @=@ 0001 @=@ Peer-reviewed +dnet:review_levels @=@ 0001 @=@ Peer-reviewed Article +dnet:review_levels @=@ 0001 @=@ Peer-reviewed Paper +dnet:review_levels @=@ 0001 @=@ Peer-reviewed Review +dnet:review_levels @=@ 0001 @=@ Peer-reviewed Review Article +dnet:review_levels @=@ 0001 @=@ Peer-reviewed Text +dnet:review_levels @=@ 0001 @=@ Peer-reviewed communication +dnet:review_levels @=@ 0001 @=@ Peer-reviewed conference proceedings +dnet:review_levels @=@ 0001 @=@ Peer-reviewed research article +dnet:review_levels @=@ 0001 @=@ Peer-reviewed short communication +dnet:review_levels @=@ 0001 @=@ PeerReviewed +dnet:review_levels @=@ 0001 @=@ Proceedings (peer-reviewed) +dnet:review_levels @=@ 0001 @=@ Refereed +dnet:review_levels @=@ 0001 @=@ Refereed Article +dnet:review_levels @=@ 0001 @=@ Research articles evaluated by double blind +dnet:review_levels @=@ 0001 @=@ Resenha avaliada pelos pares +dnet:review_levels @=@ 0001 @=@ Review article (peer-reviewed) +dnet:review_levels @=@ 0001 @=@ Reviewed by peers +dnet:review_levels @=@ 0001 @=@ Revisión por Expertos +dnet:review_levels @=@ 0001 @=@ Revisto por Pares +dnet:review_levels @=@ 0001 @=@ SBBq abstracts / peer-reviewed +dnet:review_levels @=@ 0001 @=@ SBBq resúmenes - revisada por pares +dnet:review_levels @=@ 0001 @=@ Scholarly publ. Refereed +dnet:review_levels @=@ 0001 @=@ Scientific Publ (refereed) +dnet:review_levels @=@ 0001 @=@ Vertaisarvioimaton kirjoitus tieteellisessä aikakauslehdessä +dnet:review_levels @=@ 0001 @=@ Vertaisarvioitu alkuperäisartikkeli tieteellisessä aikakauslehdessä +dnet:review_levels @=@ 0001 @=@ Vertaisarvioitu artikkeli konferenssijulkaisussa +dnet:review_levels @=@ 0001 @=@ Vertaisarvioitu artikkeli tieteellisessä aikakauslehdessä +dnet:review_levels @=@ 0001 @=@ Vertaisarvioitu kirjan tai muun kokoomateoksen osa +dnet:review_levels @=@ 0001 @=@ Wetensch. publ. Refereed +dnet:review_levels @=@ 0001 @=@ article in peer-reviewed journal +dnet:review_levels @=@ 0001 @=@ articles validés +dnet:review_levels @=@ 0001 @=@ avaliado por pares, temas livres +dnet:review_levels @=@ 0001 @=@ info:eu-repo/semantics/peerReviewed +dnet:review_levels @=@ 0001 @=@ info:ulb-repo/semantics/articlePeerReview +dnet:review_levels @=@ 0001 @=@ proceeding with peer review +dnet:review_levels @=@ 0001 @=@ refereed_publications +dnet:review_levels @=@ 0001 @=@ ul_published_reviewed +dnet:review_levels @=@ 0001 @=@ Άρθρο που έχει αξιολογηθεί από ομότιμους ειδικούς +dnet:review_levels @=@ 0001 @=@ Άρθρο το οποίο έχει περάσει από ομότιμη αξιολόγηση +dnet:review_levels @=@ 0001 @=@ レフェリー付き論文 +dnet:review_levels @=@ 0001 @=@ 印刷物/電子媒体-テクニカルレポート類(査読有り) +dnet:review_levels @=@ 0001 @=@ 印刷物/電子媒体-会議発表論文(査読有り) +dnet:review_levels @=@ 0001 @=@ 印刷物/電子媒体-図書(査読有り) +dnet:review_levels @=@ 0001 @=@ 印刷物/電子媒体-学術雑誌論文(査読有り) +dnet:review_levels @=@ 0001 @=@ 印刷物/電子媒体-紀要論文(査読有り) +dnet:review_levels @=@ 0001 @=@ 印刷物/電子媒体-雑誌記事(査読有り) +dnet:review_levels @=@ 0001 @=@ 原著論文(査読有り) +dnet:review_levels @=@ 0001 @=@ 査読論文 \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/terms.txt b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/terms.txt new file mode 100644 index 000000000..93cc00eca --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/terms.txt @@ -0,0 +1,1080 @@ +ModularUiLabels @=@ ModularUiLabels @=@ PendingRepositoryResources @=@ Pending datasource +ModularUiLabels @=@ ModularUiLabels @=@ RepositoryServiceResources @=@ Valid datasource +dnet:content_description_typologies @=@ D-Net Content Description Typologies @=@ file::EuropePMC @=@ file::EuropePMC +dnet:content_description_typologies @=@ D-Net Content Description Typologies @=@ file::PDF @=@ file::PDF +dnet:content_description_typologies @=@ D-Net Content Description Typologies @=@ file::WoS @=@ file::WoS +dnet:content_description_typologies @=@ D-Net Content Description Typologies @=@ metadata @=@ metadata +dnet:content_description_typologies @=@ D-Net Content Description Typologies @=@ file::hybrid @=@ file::hybrid +dnet:provenanceActions @=@ dnet:provenanceActions @=@ sysimport:crosswalk:cris @=@ Harvested +dnet:provenanceActions @=@ dnet:provenanceActions @=@ sysimport:actionset:orcidworks-no-doi @=@ Harvested +dnet:provenanceActions @=@ dnet:provenanceActions @=@ sysimport:crosswalk:infospace @=@ Harvested +dnet:provenanceActions @=@ dnet:provenanceActions @=@ sysimport:crosswalk @=@ Harvested +dnet:provenanceActions @=@ dnet:provenanceActions @=@ sysimport:crosswalk:aggregator @=@ Harvested +dnet:provenanceActions @=@ dnet:provenanceActions @=@ sysimport:crosswalk:datasetarchive @=@ Harvested +dnet:provenanceActions @=@ dnet:provenanceActions @=@ sysimport:actionset @=@ Harvested +dnet:provenanceActions @=@ dnet:provenanceActions @=@ sysimport:crosswalk:entityregistry @=@ Harvested +dnet:provenanceActions @=@ dnet:provenanceActions @=@ sysimport:crosswalk:repository @=@ Harvested +dnet:provenanceActions @=@ dnet:provenanceActions @=@ sysimport:mining:aggregator @=@ Inferred by OpenAIRE +dnet:provenanceActions @=@ dnet:provenanceActions @=@ community:subject @=@ Inferred by OpenAIRE +dnet:provenanceActions @=@ dnet:provenanceActions @=@ community:zenodocommunity @=@ Inferred by OpenAIRE +dnet:provenanceActions @=@ dnet:provenanceActions @=@ iis @=@ Inferred by OpenAIRE +dnet:provenanceActions @=@ dnet:provenanceActions @=@ sysimport:mining:entityregistry @=@ Inferred by OpenAIRE +dnet:provenanceActions @=@ dnet:provenanceActions @=@ community:organization @=@ Inferred by OpenAIRE +dnet:provenanceActions @=@ dnet:provenanceActions @=@ sysimport:mining:infospace @=@ Inferred by OpenAIRE +dnet:provenanceActions @=@ dnet:provenanceActions @=@ sysimport:dedup @=@ Inferred by OpenAIRE +dnet:provenanceActions @=@ dnet:provenanceActions @=@ community:datasource @=@ Inferred by OpenAIRE +dnet:provenanceActions @=@ dnet:provenanceActions @=@ propagation:project:semrel @=@ Inferred by OpenAIRE +dnet:provenanceActions @=@ dnet:provenanceActions @=@ sysimport:mining:cris @=@ Inferred by OpenAIRE +dnet:provenanceActions @=@ dnet:provenanceActions @=@ sysimport:mining:repository @=@ Inferred by OpenAIRE +dnet:provenanceActions @=@ dnet:provenanceActions @=@ sysimport:mining:datasetarchive @=@ Inferred by OpenAIRE +dnet:provenanceActions @=@ dnet:provenanceActions @=@ community:semrel @=@ Inferred by OpenAIRE +dnet:provenanceActions @=@ dnet:provenanceActions @=@ user:claim @=@ Linked by user +dnet:provenanceActions @=@ dnet:provenanceActions @=@ user:claim:pid @=@ Linked by user +dnet:provenanceActions @=@ dnet:provenanceActions @=@ user:insert @=@ Linked by user +dnet:provenanceActions @=@ dnet:provenanceActions @=@ user:claim:search @=@ Linked by user +dnet:provenanceActions @=@ dnet:provenanceActions @=@ UNKNOWN @=@ UNKNOWN +dnet:provenanceActions @=@ dnet:provenanceActions @=@ country:instrepos @=@ Inferred by OpenAIRE +dnet:access_modes @=@ dnet:access_modes @=@ 12MONTHS @=@ 12 Months Embargo +dnet:access_modes @=@ dnet:access_modes @=@ 6MONTHS @=@ 6 Months Embargo +dnet:access_modes @=@ dnet:access_modes @=@ CLOSED @=@ Closed Access +dnet:access_modes @=@ dnet:access_modes @=@ EMBARGO @=@ Embargo +dnet:access_modes @=@ dnet:access_modes @=@ OPEN @=@ Open Access +dnet:access_modes @=@ dnet:access_modes @=@ OPEN SOURCE @=@ Open Source +dnet:access_modes @=@ dnet:access_modes @=@ OTHER @=@ Other +dnet:access_modes @=@ dnet:access_modes @=@ RESTRICTED @=@ Restricted +dnet:access_modes @=@ dnet:access_modes @=@ UNKNOWN @=@ not available +fct:funding_typologies @=@ fct:funding_typologies @=@ fct:program @=@ fct:program +dnet:compatibilityLevel @=@ dnet:compatibilityLevel @=@ openaire2.0 @=@ OpenAIRE 2.0 (EC funding) +dnet:compatibilityLevel @=@ dnet:compatibilityLevel @=@ openaire3.0 @=@ OpenAIRE 3.0 (OA, funding) +dnet:compatibilityLevel @=@ dnet:compatibilityLevel @=@ driver @=@ OpenAIRE Basic (DRIVER OA) +dnet:compatibilityLevel @=@ dnet:compatibilityLevel @=@ openaire-cris_1.1 @=@ OpenAIRE CRIS v1.1 +dnet:compatibilityLevel @=@ dnet:compatibilityLevel @=@ openaire2.0_data @=@ OpenAIRE Data (funded, referenced datasets) +dnet:compatibilityLevel @=@ dnet:compatibilityLevel @=@ openaire-pub_4.0 @=@ OpenAIRE PubRepos v4.0 +dnet:compatibilityLevel @=@ dnet:compatibilityLevel @=@ hostedBy @=@ collected from a compatible aggregator +dnet:compatibilityLevel @=@ dnet:compatibilityLevel @=@ files @=@ files +dnet:compatibilityLevel @=@ dnet:compatibilityLevel @=@ native @=@ native +dnet:compatibilityLevel @=@ dnet:compatibilityLevel @=@ UNKNOWN @=@ not available +dnet:compatibilityLevel @=@ dnet:compatibilityLevel @=@ notCompatible @=@ under validation +dnet:dataCite_date @=@ dnet:dataCite_date @=@ UNKNOWN @=@ UNKNOWN +dnet:dataCite_date @=@ dnet:dataCite_date @=@ available @=@ available +dnet:dataCite_date @=@ dnet:dataCite_date @=@ copyrighted @=@ copyrighted +dnet:dataCite_date @=@ dnet:dataCite_date @=@ created @=@ created +dnet:dataCite_date @=@ dnet:dataCite_date @=@ endDate @=@ endDate +dnet:dataCite_date @=@ dnet:dataCite_date @=@ issued @=@ issued +dnet:dataCite_date @=@ dnet:dataCite_date @=@ startDate @=@ startDate +dnet:dataCite_date @=@ dnet:dataCite_date @=@ submitted @=@ submitted +dnet:dataCite_date @=@ dnet:dataCite_date @=@ updated @=@ updated +dnet:dataCite_date @=@ dnet:dataCite_date @=@ valid @=@ valid +dnet:dataCite_date @=@ dnet:dataCite_date @=@ published-print @=@ published-print +dnet:dataCite_date @=@ dnet:dataCite_date @=@ published-online @=@ published-online +dnet:dataCite_date @=@ dnet:dataCite_date @=@ accepted @=@ accepted +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ crissystem @=@ CRIS System +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ datarepository::unknown @=@ Data Repository +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ aggregator::datarepository @=@ Data Repository Aggregator +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ entityregistry::projects @=@ Funder database +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ infospace @=@ Information Space +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ pubsrepository::institutional @=@ Institutional Repository +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ aggregator::pubsrepository::institutional @=@ Institutional Repository Aggregator +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ pubsrepository::journal @=@ Journal +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ aggregator::pubsrepository::journals @=@ Journal Aggregator/Publisher +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ pubsrepository::mock @=@ Other +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ pubscatalogue::unknown @=@ Publication Catalogue +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ pubsrepository::unknown @=@ Publication Repository +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ aggregator::pubsrepository::unknown @=@ Publication Repository Aggregator +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ entityregistry @=@ Registry +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ entityregistry::repositories @=@ Registry of repositories +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ entityregistry::products @=@ Registry of research products +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ entityregistry::researchers @=@ Registry of researchers +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ entityregistry::organizations @=@ Registry of organizations +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ scholarcomminfra @=@ Scholarly Comm. Infrastructure +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ softwarerepository @=@ Software Repository +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ pubsrepository::thematic @=@ Thematic Repository +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ websource @=@ Web Source +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ aggregator::softwarerepository @=@ Software Repository Aggregator +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ orprepository @=@ Repository +dnet:datasource_typologies @=@ dnet:datasource_typologies @=@ researchgraph @=@ Research Graph +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ ACM @=@ ACM Computing Classification System +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ agrovoc @=@ AGROVOC +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ bicssc @=@ BIC standard subject categories +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ DFG @=@ DFG Classification +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ ddc @=@ Dewey Decimal Classification +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ nsf:fieldOfApplication @=@ Field of Application (NSF) +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ gok @=@ Göttingen Online Classification +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ ec:h2020topics @=@ Horizon 2020 Topics +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ IPC @=@ International Patent Classification +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ jel @=@ JEL Classification +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ lcsh @=@ Library of Congress Subject Headings +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ msc @=@ Mathematics Subject Classification +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ mesheuropmc @=@ Medical Subject Headings +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ mesh @=@ Medical Subject Headings +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ bk @=@ Nederlandse basisclassificatie +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ dnet:od_subjects @=@ OpenDOAR subjects +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ ocis @=@ Optics Classification and Indexing Scheme +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ pacs @=@ Physics and Astronomy Classification Scheme +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ rvk @=@ Regensburger Verbundklassifikation +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ UNKNOWN @=@ UNKNOWN +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ udc @=@ Universal Decimal Classification +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ wos @=@ Web of Science Subject Areas +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ arxiv @=@ arXiv +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ keyword @=@ keyword +dnet:subject_classification_typologies @=@ dnet:subject_classification_typologies @=@ MAG @=@ Microsoft Academic Graph classification +fct:contractTypes @=@ fct:contractTypes @=@ UNKNOWN @=@ UNKNOWN +dnet:publication_resource @=@ dnet:publication_resource @=@ 0018 @=@ Annotation +dnet:publication_resource @=@ dnet:publication_resource @=@ 0001 @=@ Article +dnet:publication_resource @=@ dnet:publication_resource @=@ 0033 @=@ Audiovisual +dnet:publication_resource @=@ dnet:publication_resource @=@ 0008 @=@ Bachelor thesis +dnet:publication_resource @=@ dnet:publication_resource @=@ 0046 @=@ Bioentity +dnet:publication_resource @=@ dnet:publication_resource @=@ 0002 @=@ Book +dnet:publication_resource @=@ dnet:publication_resource @=@ 0037 @=@ Clinical Trial +dnet:publication_resource @=@ dnet:publication_resource @=@ 0022 @=@ Collection +dnet:publication_resource @=@ dnet:publication_resource @=@ 0004 @=@ Conference object +dnet:publication_resource @=@ dnet:publication_resource @=@ 0005 @=@ Contribution for newspaper or weekly magazine +dnet:publication_resource @=@ dnet:publication_resource @=@ 0045 @=@ Data Management Plan +dnet:publication_resource @=@ dnet:publication_resource @=@ 0031 @=@ Data Paper +dnet:publication_resource @=@ dnet:publication_resource @=@ 0021 @=@ Dataset +dnet:publication_resource @=@ dnet:publication_resource @=@ 0006 @=@ Doctoral thesis +dnet:publication_resource @=@ dnet:publication_resource @=@ 0023 @=@ Event +dnet:publication_resource @=@ dnet:publication_resource @=@ 0009 @=@ External research report +dnet:publication_resource @=@ dnet:publication_resource @=@ 0024 @=@ Film +dnet:publication_resource @=@ dnet:publication_resource @=@ 0025 @=@ Image +dnet:publication_resource @=@ dnet:publication_resource @=@ 0026 @=@ InteractiveResource +dnet:publication_resource @=@ dnet:publication_resource @=@ 0011 @=@ Internal report +dnet:publication_resource @=@ dnet:publication_resource @=@ 0043 @=@ Journal +dnet:publication_resource @=@ dnet:publication_resource @=@ 0010 @=@ Lecture +dnet:publication_resource @=@ dnet:publication_resource @=@ 0007 @=@ Master thesis +dnet:publication_resource @=@ dnet:publication_resource @=@ 0027 @=@ Model +dnet:publication_resource @=@ dnet:publication_resource @=@ 0012 @=@ Newsletter +dnet:publication_resource @=@ dnet:publication_resource @=@ 0020 @=@ Other ORP type +dnet:publication_resource @=@ dnet:publication_resource @=@ 0039 @=@ Other dataset type +dnet:publication_resource @=@ dnet:publication_resource @=@ 0038 @=@ Other literature type +dnet:publication_resource @=@ dnet:publication_resource @=@ 0040 @=@ Other software type +dnet:publication_resource @=@ dnet:publication_resource @=@ 0013 @=@ Part of book or chapter of book +dnet:publication_resource @=@ dnet:publication_resource @=@ 0019 @=@ Patent +dnet:publication_resource @=@ dnet:publication_resource @=@ 0028 @=@ PhysicalObject +dnet:publication_resource @=@ dnet:publication_resource @=@ 0016 @=@ Preprint +dnet:publication_resource @=@ dnet:publication_resource @=@ 0034 @=@ Project deliverable +dnet:publication_resource @=@ dnet:publication_resource @=@ 0035 @=@ Project milestone +dnet:publication_resource @=@ dnet:publication_resource @=@ 0036 @=@ Project proposal +dnet:publication_resource @=@ dnet:publication_resource @=@ 0017 @=@ Report +dnet:publication_resource @=@ dnet:publication_resource @=@ 0014 @=@ Research +dnet:publication_resource @=@ dnet:publication_resource @=@ 0015 @=@ Review +dnet:publication_resource @=@ dnet:publication_resource @=@ 0029 @=@ Software +dnet:publication_resource @=@ dnet:publication_resource @=@ 0032 @=@ Software Paper +dnet:publication_resource @=@ dnet:publication_resource @=@ 0030 @=@ Sound +dnet:publication_resource @=@ dnet:publication_resource @=@ 0044 @=@ Thesis +dnet:publication_resource @=@ dnet:publication_resource @=@ 0000 @=@ Unknown +dnet:publication_resource @=@ dnet:publication_resource @=@ 0042 @=@ Virtual Appliance +ec:funding_typologies @=@ ec:funding_typologies @=@ ec:frameworkprogram @=@ frameworkprogram +ec:funding_typologies @=@ ec:funding_typologies @=@ ec:program @=@ program +ec:funding_typologies @=@ ec:funding_typologies @=@ ec:specificprogram @=@ specificprogram +ec:FP7contractTypes @=@ ec:FP7contractTypes @=@ 171 @=@ Article 171 of the Treaty +ec:FP7contractTypes @=@ ec:FP7contractTypes @=@ BSG @=@ Research for the benefit of specific groups +ec:FP7contractTypes @=@ ec:FP7contractTypes @=@ CIP-EIP-TN @=@ CIP-Eco-Innovation - CIP-Thematic Network +ec:FP7contractTypes @=@ ec:FP7contractTypes @=@ CP @=@ Collaborative project +ec:FP7contractTypes @=@ ec:FP7contractTypes @=@ CP-CSA @=@ Combination of CP & CSA +ec:FP7contractTypes @=@ ec:FP7contractTypes @=@ CSA @=@ Coordination and support action +ec:FP7contractTypes @=@ ec:FP7contractTypes @=@ ERC @=@ Support for frontier research (ERC) +ec:FP7contractTypes @=@ ec:FP7contractTypes @=@ MC @=@ Support for training and career development of researchers (Marie Curie) +ec:FP7contractTypes @=@ ec:FP7contractTypes @=@ NoE @=@ Network of Excellence +wt:funding_relations @=@ wt:funding_relations @=@ wt:hasParentFunding @=@ wt:hasParentFunding +dnet:languages @=@ dnet:languages @=@ abk @=@ Abkhazian +dnet:languages @=@ dnet:languages @=@ ace @=@ Achinese +dnet:languages @=@ dnet:languages @=@ ach @=@ Acoli +dnet:languages @=@ dnet:languages @=@ ada @=@ Adangme +dnet:languages @=@ dnet:languages @=@ aar @=@ Afar +dnet:languages @=@ dnet:languages @=@ afh @=@ Afrihili +dnet:languages @=@ dnet:languages @=@ afr @=@ Afrikaans +dnet:languages @=@ dnet:languages @=@ afa @=@ Afro-Asiatic +dnet:languages @=@ dnet:languages @=@ aka @=@ Akan +dnet:languages @=@ dnet:languages @=@ akk @=@ Akkadian +dnet:languages @=@ dnet:languages @=@ alb/sqi @=@ Albanian +dnet:languages @=@ dnet:languages @=@ ale @=@ Aleut +dnet:languages @=@ dnet:languages @=@ alg @=@ Algonquian languages +dnet:languages @=@ dnet:languages @=@ tut @=@ Altaic +dnet:languages @=@ dnet:languages @=@ amh @=@ Amharic +dnet:languages @=@ dnet:languages @=@ egy @=@ Ancient Egyptian +dnet:languages @=@ dnet:languages @=@ grc @=@ Ancient Greek +dnet:languages @=@ dnet:languages @=@ apa @=@ Apache +dnet:languages @=@ dnet:languages @=@ ara @=@ Arabic +dnet:languages @=@ dnet:languages @=@ arg @=@ Aragonese +dnet:languages @=@ dnet:languages @=@ arc @=@ Aramaic +dnet:languages @=@ dnet:languages @=@ arp @=@ Arapaho +dnet:languages @=@ dnet:languages @=@ arn @=@ Araucanian +dnet:languages @=@ dnet:languages @=@ arw @=@ Arawak +dnet:languages @=@ dnet:languages @=@ arm/hye @=@ Armenian +dnet:languages @=@ dnet:languages @=@ art @=@ Artificial +dnet:languages @=@ dnet:languages @=@ asm @=@ Assamese +dnet:languages @=@ dnet:languages @=@ ath @=@ Athapascan +dnet:languages @=@ dnet:languages @=@ map @=@ Austronesian +dnet:languages @=@ dnet:languages @=@ ina @=@ Auxiliary Language Association) +dnet:languages @=@ dnet:languages @=@ ava @=@ Avaric +dnet:languages @=@ dnet:languages @=@ ave @=@ Avestan +dnet:languages @=@ dnet:languages @=@ awa @=@ Awadhi +dnet:languages @=@ dnet:languages @=@ aym @=@ Aymara +dnet:languages @=@ dnet:languages @=@ aze @=@ Azerbaijani +dnet:languages @=@ dnet:languages @=@ nah @=@ Aztec +dnet:languages @=@ dnet:languages @=@ ban @=@ Balinese +dnet:languages @=@ dnet:languages @=@ bat @=@ Baltic +dnet:languages @=@ dnet:languages @=@ bal @=@ Baluchi +dnet:languages @=@ dnet:languages @=@ bam @=@ Bambara +dnet:languages @=@ dnet:languages @=@ bai @=@ Bamileke +dnet:languages @=@ dnet:languages @=@ bad @=@ Banda +dnet:languages @=@ dnet:languages @=@ bnt @=@ Bantu +dnet:languages @=@ dnet:languages @=@ bas @=@ Basa +dnet:languages @=@ dnet:languages @=@ bak @=@ Bashkir +dnet:languages @=@ dnet:languages @=@ baq/eus @=@ Basque +dnet:languages @=@ dnet:languages @=@ bej @=@ Beja +dnet:languages @=@ dnet:languages @=@ bel @=@ Belarusian +dnet:languages @=@ dnet:languages @=@ bem @=@ Bemba +dnet:languages @=@ dnet:languages @=@ ben @=@ Bengali +dnet:languages @=@ dnet:languages @=@ ber @=@ Berber +dnet:languages @=@ dnet:languages @=@ bho @=@ Bhojpuri +dnet:languages @=@ dnet:languages @=@ bih @=@ Bihari +dnet:languages @=@ dnet:languages @=@ bik @=@ Bikol +dnet:languages @=@ dnet:languages @=@ bin @=@ Bini +dnet:languages @=@ dnet:languages @=@ bis @=@ Bislama +dnet:languages @=@ dnet:languages @=@ nob @=@ Bokmål, Norwegian; Norwegian Bokmål +dnet:languages @=@ dnet:languages @=@ bos @=@ Bosnian +dnet:languages @=@ dnet:languages @=@ bra @=@ Braj +dnet:languages @=@ dnet:languages @=@ bre @=@ Breton +dnet:languages @=@ dnet:languages @=@ bug @=@ Buginese +dnet:languages @=@ dnet:languages @=@ bul @=@ Bulgarian +dnet:languages @=@ dnet:languages @=@ bua @=@ Buriat +dnet:languages @=@ dnet:languages @=@ bur/mya @=@ Burmese +dnet:languages @=@ dnet:languages @=@ cad @=@ Caddo +dnet:languages @=@ dnet:languages @=@ car @=@ Carib +dnet:languages @=@ dnet:languages @=@ cat @=@ Catalan; Valencian +dnet:languages @=@ dnet:languages @=@ cau @=@ Caucasian +dnet:languages @=@ dnet:languages @=@ ceb @=@ Cebuano +dnet:languages @=@ dnet:languages @=@ cel @=@ Celtic +dnet:languages @=@ dnet:languages @=@ cai @=@ Central American Indian +dnet:languages @=@ dnet:languages @=@ chg @=@ Chagatai +dnet:languages @=@ dnet:languages @=@ cha @=@ Chamorro +dnet:languages @=@ dnet:languages @=@ che @=@ Chechen +dnet:languages @=@ dnet:languages @=@ chr @=@ Cherokee +dnet:languages @=@ dnet:languages @=@ nya @=@ Chewa; Chichewa; Nyanja +dnet:languages @=@ dnet:languages @=@ chy @=@ Cheyenne +dnet:languages @=@ dnet:languages @=@ chb @=@ Chibcha +dnet:languages @=@ dnet:languages @=@ chi/zho @=@ Chinese +dnet:languages @=@ dnet:languages @=@ chn @=@ Chinook jargon +dnet:languages @=@ dnet:languages @=@ cho @=@ Choctaw +dnet:languages @=@ dnet:languages @=@ chu @=@ Church Slavic; Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic +dnet:languages @=@ dnet:languages @=@ chv @=@ Chuvash +dnet:languages @=@ dnet:languages @=@ cop @=@ Coptic +dnet:languages @=@ dnet:languages @=@ cor @=@ Cornish +dnet:languages @=@ dnet:languages @=@ cos @=@ Corsican +dnet:languages @=@ dnet:languages @=@ cre @=@ Cree +dnet:languages @=@ dnet:languages @=@ mus @=@ Creek +dnet:languages @=@ dnet:languages @=@ crp @=@ Creoles and Pidgins +dnet:languages @=@ dnet:languages @=@ hrv @=@ Croatian +dnet:languages @=@ dnet:languages @=@ cus @=@ Cushitic +dnet:languages @=@ dnet:languages @=@ ces/cze @=@ Czech +dnet:languages @=@ dnet:languages @=@ dak @=@ Dakota +dnet:languages @=@ dnet:languages @=@ dan @=@ Danish +dnet:languages @=@ dnet:languages @=@ del @=@ Delaware +dnet:languages @=@ dnet:languages @=@ din @=@ Dinka +dnet:languages @=@ dnet:languages @=@ div @=@ Divehi +dnet:languages @=@ dnet:languages @=@ doi @=@ Dogri +dnet:languages @=@ dnet:languages @=@ dra @=@ Dravidian +dnet:languages @=@ dnet:languages @=@ dua @=@ Duala +dnet:languages @=@ dnet:languages @=@ dut/nld @=@ Dutch; Flemish +dnet:languages @=@ dnet:languages @=@ dyu @=@ Dyula +dnet:languages @=@ dnet:languages @=@ dzo @=@ Dzongkha +dnet:languages @=@ dnet:languages @=@ efi @=@ Efik +dnet:languages @=@ dnet:languages @=@ eka @=@ Ekajuk +dnet:languages @=@ dnet:languages @=@ elx @=@ Elamite +dnet:languages @=@ dnet:languages @=@ eng @=@ English +dnet:languages @=@ dnet:languages @=@ cpe @=@ English-based Creoles and Pidgins +dnet:languages @=@ dnet:languages @=@ esk @=@ Eskimo +dnet:languages @=@ dnet:languages @=@ epo @=@ Esperanto +dnet:languages @=@ dnet:languages @=@ est @=@ Estonian +dnet:languages @=@ dnet:languages @=@ ewe @=@ Ewe +dnet:languages @=@ dnet:languages @=@ ewo @=@ Ewondo +dnet:languages @=@ dnet:languages @=@ fan @=@ Fang +dnet:languages @=@ dnet:languages @=@ fat @=@ Fanti +dnet:languages @=@ dnet:languages @=@ fao @=@ Faroese +dnet:languages @=@ dnet:languages @=@ fij @=@ Fijian +dnet:languages @=@ dnet:languages @=@ fin @=@ Finnish +dnet:languages @=@ dnet:languages @=@ fiu @=@ Finno-Ugrian +dnet:languages @=@ dnet:languages @=@ fon @=@ Fon +dnet:languages @=@ dnet:languages @=@ fra/fre @=@ French +dnet:languages @=@ dnet:languages @=@ cpf @=@ French-based Creoles and Pidgins +dnet:languages @=@ dnet:languages @=@ fry @=@ Frisian +dnet:languages @=@ dnet:languages @=@ ful @=@ Fulah +dnet:languages @=@ dnet:languages @=@ gaa @=@ Ga +dnet:languages @=@ dnet:languages @=@ gae/gdh @=@ Gaelic +dnet:languages @=@ dnet:languages @=@ gla @=@ Gaelic; Scottish Gaelic +dnet:languages @=@ dnet:languages @=@ glg @=@ Galician +dnet:languages @=@ dnet:languages @=@ lug @=@ Ganda +dnet:languages @=@ dnet:languages @=@ gay @=@ Gayo +dnet:languages @=@ dnet:languages @=@ gez @=@ Geez +dnet:languages @=@ dnet:languages @=@ geo/kat @=@ Georgian +dnet:languages @=@ dnet:languages @=@ deu/ger @=@ German +dnet:languages @=@ dnet:languages @=@ gem @=@ Germanic +dnet:languages @=@ dnet:languages @=@ kik @=@ Gikuyu; Kikuyu +dnet:languages @=@ dnet:languages @=@ gil @=@ Gilbertese +dnet:languages @=@ dnet:languages @=@ gon @=@ Gondi +dnet:languages @=@ dnet:languages @=@ got @=@ Gothic +dnet:languages @=@ dnet:languages @=@ grb @=@ Grebo +dnet:languages @=@ dnet:languages @=@ ell/gre @=@ Greek +dnet:languages @=@ dnet:languages @=@ gre/ell @=@ Greek, Modern (1453-) +dnet:languages @=@ dnet:languages @=@ kal @=@ Greenlandic; Kalaallisut +dnet:languages @=@ dnet:languages @=@ grn @=@ Guarani +dnet:languages @=@ dnet:languages @=@ guj @=@ Gujarati +dnet:languages @=@ dnet:languages @=@ hai @=@ Haida +dnet:languages @=@ dnet:languages @=@ hat @=@ Haitian; Haitian Creole +dnet:languages @=@ dnet:languages @=@ hau @=@ Hausa +dnet:languages @=@ dnet:languages @=@ haw @=@ Hawaiian +dnet:languages @=@ dnet:languages @=@ heb @=@ Hebrew +dnet:languages @=@ dnet:languages @=@ her @=@ Herero +dnet:languages @=@ dnet:languages @=@ hil @=@ Hiligaynon +dnet:languages @=@ dnet:languages @=@ him @=@ Himachali +dnet:languages @=@ dnet:languages @=@ hin @=@ Hindi +dnet:languages @=@ dnet:languages @=@ hmo @=@ Hiri Motu +dnet:languages @=@ dnet:languages @=@ hun @=@ Hungarian +dnet:languages @=@ dnet:languages @=@ hup @=@ Hupa +dnet:languages @=@ dnet:languages @=@ iba @=@ Iban +dnet:languages @=@ dnet:languages @=@ ice/isl @=@ Icelandic +dnet:languages @=@ dnet:languages @=@ ido @=@ Ido +dnet:languages @=@ dnet:languages @=@ ibo @=@ Igbo +dnet:languages @=@ dnet:languages @=@ ijo @=@ Ijo +dnet:languages @=@ dnet:languages @=@ ilo @=@ Iloko +dnet:languages @=@ dnet:languages @=@ inc @=@ Indic +dnet:languages @=@ dnet:languages @=@ ine @=@ Indo-European +dnet:languages @=@ dnet:languages @=@ ind @=@ Indonesian +dnet:languages @=@ dnet:languages @=@ ile @=@ Interlingue +dnet:languages @=@ dnet:languages @=@ iku @=@ Inuktitut +dnet:languages @=@ dnet:languages @=@ ipk @=@ Inupiaq +dnet:languages @=@ dnet:languages @=@ ira @=@ Iranian +dnet:languages @=@ dnet:languages @=@ gai/iri @=@ Irish +dnet:languages @=@ dnet:languages @=@ iro @=@ Iroquoian +dnet:languages @=@ dnet:languages @=@ ita @=@ Italian +dnet:languages @=@ dnet:languages @=@ jpn @=@ Japanese +dnet:languages @=@ dnet:languages @=@ jav @=@ Javanese +dnet:languages @=@ dnet:languages @=@ jrb @=@ Judeo-Arabic +dnet:languages @=@ dnet:languages @=@ jpr @=@ Judeo-Persian +dnet:languages @=@ dnet:languages @=@ kab @=@ Kabyle +dnet:languages @=@ dnet:languages @=@ kac @=@ Kachin +dnet:languages @=@ dnet:languages @=@ kam @=@ Kamba +dnet:languages @=@ dnet:languages @=@ kan @=@ Kannada +dnet:languages @=@ dnet:languages @=@ kau @=@ Kanuri +dnet:languages @=@ dnet:languages @=@ kaa @=@ Kara-Kalpak +dnet:languages @=@ dnet:languages @=@ kar @=@ Karen +dnet:languages @=@ dnet:languages @=@ kas @=@ Kashmiri +dnet:languages @=@ dnet:languages @=@ kaw @=@ Kawi +dnet:languages @=@ dnet:languages @=@ kaz @=@ Kazakh +dnet:languages @=@ dnet:languages @=@ kha @=@ Khasi +dnet:languages @=@ dnet:languages @=@ khm @=@ Khmer +dnet:languages @=@ dnet:languages @=@ khi @=@ Khoisan +dnet:languages @=@ dnet:languages @=@ kho @=@ Khotanese +dnet:languages @=@ dnet:languages @=@ kin @=@ Kinyarwanda +dnet:languages @=@ dnet:languages @=@ kir @=@ Kirghiz +dnet:languages @=@ dnet:languages @=@ kom @=@ Komi +dnet:languages @=@ dnet:languages @=@ kon @=@ Kongo +dnet:languages @=@ dnet:languages @=@ kok @=@ Konkani +dnet:languages @=@ dnet:languages @=@ kor @=@ Korean +dnet:languages @=@ dnet:languages @=@ kpe @=@ Kpelle +dnet:languages @=@ dnet:languages @=@ kro @=@ Kru +dnet:languages @=@ dnet:languages @=@ kua @=@ Kuanyama; Kwanyama +dnet:languages @=@ dnet:languages @=@ kum @=@ Kumyk +dnet:languages @=@ dnet:languages @=@ kur @=@ Kurdish +dnet:languages @=@ dnet:languages @=@ kru @=@ Kurukh +dnet:languages @=@ dnet:languages @=@ kus @=@ Kusaie +dnet:languages @=@ dnet:languages @=@ kut @=@ Kutenai +dnet:languages @=@ dnet:languages @=@ lad @=@ Ladino +dnet:languages @=@ dnet:languages @=@ lah @=@ Lahnda +dnet:languages @=@ dnet:languages @=@ lam @=@ Lamba +dnet:languages @=@ dnet:languages @=@ lao @=@ Lao +dnet:languages @=@ dnet:languages @=@ lat @=@ Latin +dnet:languages @=@ dnet:languages @=@ lav @=@ Latvian +dnet:languages @=@ dnet:languages @=@ ltz @=@ Letzeburgesch; Luxembourgish +dnet:languages @=@ dnet:languages @=@ lez @=@ Lezghian +dnet:languages @=@ dnet:languages @=@ lim @=@ Limburgan; Limburger; Limburgish +dnet:languages @=@ dnet:languages @=@ lin @=@ Lingala +dnet:languages @=@ dnet:languages @=@ lit @=@ Lithuanian +dnet:languages @=@ dnet:languages @=@ loz @=@ Lozi +dnet:languages @=@ dnet:languages @=@ lub @=@ Luba-Katanga +dnet:languages @=@ dnet:languages @=@ lui @=@ Luiseno +dnet:languages @=@ dnet:languages @=@ lun @=@ Lunda +dnet:languages @=@ dnet:languages @=@ luo @=@ Luo +dnet:languages @=@ dnet:languages @=@ mac/mak @=@ Macedonian +dnet:languages @=@ dnet:languages @=@ mad @=@ Madurese +dnet:languages @=@ dnet:languages @=@ mag @=@ Magahi +dnet:languages @=@ dnet:languages @=@ mai @=@ Maithili +dnet:languages @=@ dnet:languages @=@ mak @=@ Makasar +dnet:languages @=@ dnet:languages @=@ mlg @=@ Malagasy +dnet:languages @=@ dnet:languages @=@ may/msa @=@ Malay +dnet:languages @=@ dnet:languages @=@ mal @=@ Malayalam +dnet:languages @=@ dnet:languages @=@ mlt @=@ Maltese +dnet:languages @=@ dnet:languages @=@ man @=@ Mandingo +dnet:languages @=@ dnet:languages @=@ mni @=@ Manipuri +dnet:languages @=@ dnet:languages @=@ mno @=@ Manobo +dnet:languages @=@ dnet:languages @=@ glv @=@ Manx +dnet:languages @=@ dnet:languages @=@ mao/mri @=@ Maori +dnet:languages @=@ dnet:languages @=@ mar @=@ Marathi +dnet:languages @=@ dnet:languages @=@ chm @=@ Mari +dnet:languages @=@ dnet:languages @=@ mah @=@ Marshallese +dnet:languages @=@ dnet:languages @=@ mwr @=@ Marwari +dnet:languages @=@ dnet:languages @=@ mas @=@ Masai +dnet:languages @=@ dnet:languages @=@ myn @=@ Mayan +dnet:languages @=@ dnet:languages @=@ men @=@ Mende +dnet:languages @=@ dnet:languages @=@ mic @=@ Micmac +dnet:languages @=@ dnet:languages @=@ dum @=@ Middle Dutch +dnet:languages @=@ dnet:languages @=@ enm @=@ Middle English +dnet:languages @=@ dnet:languages @=@ frm @=@ Middle French +dnet:languages @=@ dnet:languages @=@ gmh @=@ Middle High German +dnet:languages @=@ dnet:languages @=@ mga @=@ Middle Irish +dnet:languages @=@ dnet:languages @=@ min @=@ Minangkabau +dnet:languages @=@ dnet:languages @=@ mis @=@ Miscellaneous +dnet:languages @=@ dnet:languages @=@ moh @=@ Mohawk +dnet:languages @=@ dnet:languages @=@ mol @=@ Moldavian +dnet:languages @=@ dnet:languages @=@ mkh @=@ Mon-Kmer +dnet:languages @=@ dnet:languages @=@ lol @=@ Mongo +dnet:languages @=@ dnet:languages @=@ mon @=@ Mongolian +dnet:languages @=@ dnet:languages @=@ mos @=@ Mossi +dnet:languages @=@ dnet:languages @=@ mul @=@ Multiple languages +dnet:languages @=@ dnet:languages @=@ mun @=@ Munda +dnet:languages @=@ dnet:languages @=@ nau @=@ Nauru +dnet:languages @=@ dnet:languages @=@ nav @=@ Navajo; Navaho +dnet:languages @=@ dnet:languages @=@ nde @=@ Ndebele, North +dnet:languages @=@ dnet:languages @=@ nbl @=@ Ndebele, South +dnet:languages @=@ dnet:languages @=@ ndo @=@ Ndonga +dnet:languages @=@ dnet:languages @=@ nep @=@ Nepali +dnet:languages @=@ dnet:languages @=@ new @=@ Newari +dnet:languages @=@ dnet:languages @=@ nic @=@ Niger-Kordofanian +dnet:languages @=@ dnet:languages @=@ ssa @=@ Nilo-Saharan +dnet:languages @=@ dnet:languages @=@ niu @=@ Niuean +dnet:languages @=@ dnet:languages @=@ non @=@ Norse +dnet:languages @=@ dnet:languages @=@ nai @=@ North American Indian +dnet:languages @=@ dnet:languages @=@ sme @=@ Northern Sami +dnet:languages @=@ dnet:languages @=@ nor @=@ Norwegian +dnet:languages @=@ dnet:languages @=@ nno @=@ Norwegian Nynorsk; Nynorsk, Norwegian +dnet:languages @=@ dnet:languages @=@ nub @=@ Nubian +dnet:languages @=@ dnet:languages @=@ nym @=@ Nyamwezi +dnet:languages @=@ dnet:languages @=@ nyn @=@ Nyankole +dnet:languages @=@ dnet:languages @=@ nyo @=@ Nyoro +dnet:languages @=@ dnet:languages @=@ nzi @=@ Nzima +dnet:languages @=@ dnet:languages @=@ oci @=@ Occitan (post 1500); Provençal +dnet:languages @=@ dnet:languages @=@ oji @=@ Ojibwa +dnet:languages @=@ dnet:languages @=@ ang @=@ Old English +dnet:languages @=@ dnet:languages @=@ fro @=@ Old French +dnet:languages @=@ dnet:languages @=@ goh @=@ Old High German +dnet:languages @=@ dnet:languages @=@ ori @=@ Oriya +dnet:languages @=@ dnet:languages @=@ orm @=@ Oromo +dnet:languages @=@ dnet:languages @=@ osa @=@ Osage +dnet:languages @=@ dnet:languages @=@ oss @=@ Ossetian; Ossetic +dnet:languages @=@ dnet:languages @=@ oto @=@ Otomian +dnet:languages @=@ dnet:languages @=@ ota @=@ Ottoman +dnet:languages @=@ dnet:languages @=@ pal @=@ Pahlavi +dnet:languages @=@ dnet:languages @=@ pau @=@ Palauan +dnet:languages @=@ dnet:languages @=@ pli @=@ Pali +dnet:languages @=@ dnet:languages @=@ pam @=@ Pampanga +dnet:languages @=@ dnet:languages @=@ pag @=@ Pangasinan +dnet:languages @=@ dnet:languages @=@ pan @=@ Panjabi; Punjabi +dnet:languages @=@ dnet:languages @=@ pap @=@ Papiamento +dnet:languages @=@ dnet:languages @=@ paa @=@ Papuan-Australian +dnet:languages @=@ dnet:languages @=@ fas/per @=@ Persian +dnet:languages @=@ dnet:languages @=@ peo @=@ Persian, Old (ca 600 - 400 B.C.) +dnet:languages @=@ dnet:languages @=@ phn @=@ Phoenician +dnet:languages @=@ dnet:languages @=@ pol @=@ Polish +dnet:languages @=@ dnet:languages @=@ pon @=@ Ponape +dnet:languages @=@ dnet:languages @=@ por @=@ Portuguese +dnet:languages @=@ dnet:languages @=@ cpp @=@ Portuguese-based Creoles and Pidgins +dnet:languages @=@ dnet:languages @=@ pra @=@ Prakrit +dnet:languages @=@ dnet:languages @=@ pro @=@ Provencal +dnet:languages @=@ dnet:languages @=@ pus @=@ Pushto +dnet:languages @=@ dnet:languages @=@ que @=@ Quechua +dnet:languages @=@ dnet:languages @=@ roh @=@ Raeto-Romance +dnet:languages @=@ dnet:languages @=@ raj @=@ Rajasthani +dnet:languages @=@ dnet:languages @=@ rar @=@ Rarotongan +dnet:languages @=@ dnet:languages @=@ roa @=@ Romance +dnet:languages @=@ dnet:languages @=@ ron/rum @=@ Romanian +dnet:languages @=@ dnet:languages @=@ rom @=@ Romany +dnet:languages @=@ dnet:languages @=@ run @=@ Rundi +dnet:languages @=@ dnet:languages @=@ rus @=@ Russian +dnet:languages @=@ dnet:languages @=@ sal @=@ Salishan +dnet:languages @=@ dnet:languages @=@ sam @=@ Samaritan +dnet:languages @=@ dnet:languages @=@ smi @=@ Sami +dnet:languages @=@ dnet:languages @=@ smo @=@ Samoan +dnet:languages @=@ dnet:languages @=@ sad @=@ Sandawe +dnet:languages @=@ dnet:languages @=@ sag @=@ Sango +dnet:languages @=@ dnet:languages @=@ san @=@ Sanskrit +dnet:languages @=@ dnet:languages @=@ srd @=@ Sardinian +dnet:languages @=@ dnet:languages @=@ sco @=@ Scots +dnet:languages @=@ dnet:languages @=@ sel @=@ Selkup +dnet:languages @=@ dnet:languages @=@ sem @=@ Semitic +dnet:languages @=@ dnet:languages @=@ srp @=@ Serbian +dnet:languages @=@ dnet:languages @=@ scr @=@ Serbo-Croatian +dnet:languages @=@ dnet:languages @=@ srr @=@ Serer +dnet:languages @=@ dnet:languages @=@ shn @=@ Shan +dnet:languages @=@ dnet:languages @=@ sna @=@ Shona +dnet:languages @=@ dnet:languages @=@ iii @=@ Sichuan Yi +dnet:languages @=@ dnet:languages @=@ sid @=@ Sidamo +dnet:languages @=@ dnet:languages @=@ bla @=@ Siksika +dnet:languages @=@ dnet:languages @=@ snd @=@ Sindhi +dnet:languages @=@ dnet:languages @=@ sin @=@ Sinhala; Sinhalese +dnet:languages @=@ dnet:languages @=@ sit @=@ Sino-Tibetan +dnet:languages @=@ dnet:languages @=@ sio @=@ Siouan +dnet:languages @=@ dnet:languages @=@ sla @=@ Slavic +dnet:languages @=@ dnet:languages @=@ slk/slo @=@ Slovak +dnet:languages @=@ dnet:languages @=@ slv @=@ Slovenian +dnet:languages @=@ dnet:languages @=@ sog @=@ Sogdian +dnet:languages @=@ dnet:languages @=@ som @=@ Somali +dnet:languages @=@ dnet:languages @=@ son @=@ Songhai +dnet:languages @=@ dnet:languages @=@ wen @=@ Sorbian +dnet:languages @=@ dnet:languages @=@ nso @=@ Sotho +dnet:languages @=@ dnet:languages @=@ sot @=@ Sotho, Southern +dnet:languages @=@ dnet:languages @=@ sai @=@ South American Indian +dnet:languages @=@ dnet:languages @=@ esl/spa @=@ Spanish +dnet:languages @=@ dnet:languages @=@ spa @=@ Spanish; Castilian +dnet:languages @=@ dnet:languages @=@ suk @=@ Sukuma +dnet:languages @=@ dnet:languages @=@ sux @=@ Sumerian +dnet:languages @=@ dnet:languages @=@ sun @=@ Sundanese +dnet:languages @=@ dnet:languages @=@ sus @=@ Susu +dnet:languages @=@ dnet:languages @=@ swa @=@ Swahili +dnet:languages @=@ dnet:languages @=@ ssw @=@ Swati +dnet:languages @=@ dnet:languages @=@ swe @=@ Swedish +dnet:languages @=@ dnet:languages @=@ syr @=@ Syriac +dnet:languages @=@ dnet:languages @=@ tgl @=@ Tagalog +dnet:languages @=@ dnet:languages @=@ tah @=@ Tahitian +dnet:languages @=@ dnet:languages @=@ tgk @=@ Tajik +dnet:languages @=@ dnet:languages @=@ tmh @=@ Tamashek +dnet:languages @=@ dnet:languages @=@ tam @=@ Tamil +dnet:languages @=@ dnet:languages @=@ tat @=@ Tatar +dnet:languages @=@ dnet:languages @=@ tel @=@ Telugu +dnet:languages @=@ dnet:languages @=@ ter @=@ Tereno +dnet:languages @=@ dnet:languages @=@ tha @=@ Thai +dnet:languages @=@ dnet:languages @=@ bod/tib @=@ Tibetan +dnet:languages @=@ dnet:languages @=@ tig @=@ Tigre +dnet:languages @=@ dnet:languages @=@ tir @=@ Tigrinya +dnet:languages @=@ dnet:languages @=@ tem @=@ Timne +dnet:languages @=@ dnet:languages @=@ tiv @=@ Tivi +dnet:languages @=@ dnet:languages @=@ tli @=@ Tlingit +dnet:languages @=@ dnet:languages @=@ ton @=@ Tonga (Tonga Islands) +dnet:languages @=@ dnet:languages @=@ tog @=@ Tonga(Nyasa) +dnet:languages @=@ dnet:languages @=@ tru @=@ Truk +dnet:languages @=@ dnet:languages @=@ tsi @=@ Tsimshian +dnet:languages @=@ dnet:languages @=@ tso @=@ Tsonga +dnet:languages @=@ dnet:languages @=@ tsn @=@ Tswana +dnet:languages @=@ dnet:languages @=@ tum @=@ Tumbuka +dnet:languages @=@ dnet:languages @=@ tur @=@ Turkish +dnet:languages @=@ dnet:languages @=@ tuk @=@ Turkmen +dnet:languages @=@ dnet:languages @=@ tyv @=@ Tuvinian +dnet:languages @=@ dnet:languages @=@ twi @=@ Twi +dnet:languages @=@ dnet:languages @=@ uga @=@ Ugaritic +dnet:languages @=@ dnet:languages @=@ uig @=@ Uighur; Uyghur +dnet:languages @=@ dnet:languages @=@ ukr @=@ Ukrainian +dnet:languages @=@ dnet:languages @=@ umb @=@ Umbundu +dnet:languages @=@ dnet:languages @=@ und @=@ Undetermined +dnet:languages @=@ dnet:languages @=@ urd @=@ Urdu +dnet:languages @=@ dnet:languages @=@ uzb @=@ Uzbek +dnet:languages @=@ dnet:languages @=@ vai @=@ Vai +dnet:languages @=@ dnet:languages @=@ ven @=@ Venda +dnet:languages @=@ dnet:languages @=@ vie @=@ Vietnamese +dnet:languages @=@ dnet:languages @=@ vol @=@ Volapük +dnet:languages @=@ dnet:languages @=@ vot @=@ Votic +dnet:languages @=@ dnet:languages @=@ wak @=@ Wakashan +dnet:languages @=@ dnet:languages @=@ wal @=@ Walamo +dnet:languages @=@ dnet:languages @=@ wln @=@ Walloon +dnet:languages @=@ dnet:languages @=@ war @=@ Waray +dnet:languages @=@ dnet:languages @=@ was @=@ Washo +dnet:languages @=@ dnet:languages @=@ cym/wel @=@ Welsh +dnet:languages @=@ dnet:languages @=@ wol @=@ Wolof +dnet:languages @=@ dnet:languages @=@ xho @=@ Xhosa +dnet:languages @=@ dnet:languages @=@ sah @=@ Yakut +dnet:languages @=@ dnet:languages @=@ yao @=@ Yao +dnet:languages @=@ dnet:languages @=@ yap @=@ Yap +dnet:languages @=@ dnet:languages @=@ yid @=@ Yiddish +dnet:languages @=@ dnet:languages @=@ yor @=@ Yoruba +dnet:languages @=@ dnet:languages @=@ zap @=@ Zapotec +dnet:languages @=@ dnet:languages @=@ zen @=@ Zenaga +dnet:languages @=@ dnet:languages @=@ zha @=@ Zhuang; Chuang +dnet:languages @=@ dnet:languages @=@ zul @=@ Zulu +dnet:languages @=@ dnet:languages @=@ zun @=@ Zuni +dnet:languages @=@ dnet:languages @=@ sga @=@ old Irish +nsf:contractTypes @=@ NSF Contract Types @=@ BOA/Task Order @=@ BOA/Task Order +nsf:contractTypes @=@ NSF Contract Types @=@ Continuing grant @=@ Continuing grant +nsf:contractTypes @=@ NSF Contract Types @=@ Contract @=@ Contract +nsf:contractTypes @=@ NSF Contract Types @=@ Contract Interagency Agreement @=@ Contract Interagency Agreement +nsf:contractTypes @=@ NSF Contract Types @=@ Cooperative Agreement @=@ Cooperative Agreement +nsf:contractTypes @=@ NSF Contract Types @=@ Fellowship @=@ Fellowship +nsf:contractTypes @=@ NSF Contract Types @=@ Fixed Price Award @=@ Fixed Price Award +nsf:contractTypes @=@ NSF Contract Types @=@ GAA @=@ GAA +nsf:contractTypes @=@ NSF Contract Types @=@ Interagency Agreement @=@ Interagency Agreement +nsf:contractTypes @=@ NSF Contract Types @=@ Intergovernmental Personnel Award @=@ Intergovernmental Personnel Award +nsf:contractTypes @=@ NSF Contract Types @=@ Personnel Agreement @=@ Personnel Agreement +nsf:contractTypes @=@ NSF Contract Types @=@ Standard Grant @=@ Standard Grant +ec:funding_relations @=@ ec:funding_relations @=@ ec:hasframeworkprogram @=@ hasframeworkprogram +ec:funding_relations @=@ ec:funding_relations @=@ ec:hasprogram @=@ hasprogram +ec:funding_relations @=@ ec:funding_relations @=@ ec:hasspecificprogram @=@ hasspecificprogram +dnet:dataCite_resource @=@ dnet:dataCite_resource @=@ UNKNOWN @=@ UNKNOWN +dnet:dataCite_resource @=@ dnet:dataCite_resource @=@ collection @=@ collection +dnet:dataCite_resource @=@ dnet:dataCite_resource @=@ dataset @=@ dataset +dnet:dataCite_resource @=@ dnet:dataCite_resource @=@ event @=@ event +dnet:dataCite_resource @=@ dnet:dataCite_resource @=@ film @=@ film +dnet:dataCite_resource @=@ dnet:dataCite_resource @=@ image @=@ image +dnet:dataCite_resource @=@ dnet:dataCite_resource @=@ interactiveResource @=@ interactiveResource +dnet:dataCite_resource @=@ dnet:dataCite_resource @=@ model @=@ model +dnet:dataCite_resource @=@ dnet:dataCite_resource @=@ physicalObject @=@ physicalObject +dnet:dataCite_resource @=@ dnet:dataCite_resource @=@ service @=@ service +dnet:dataCite_resource @=@ dnet:dataCite_resource @=@ software @=@ software +dnet:dataCite_resource @=@ dnet:dataCite_resource @=@ sound @=@ sound +dnet:dataCite_resource @=@ dnet:dataCite_resource @=@ text @=@ text +dnet:dataCite_resource @=@ dnet:dataCite_resource @=@ clinicalTrial @=@ Clinical trial +dnet:dataCite_title @=@ dnet:dataCite_title @=@ alternative title @=@ alternative title +dnet:dataCite_title @=@ dnet:dataCite_title @=@ main title @=@ main title +dnet:dataCite_title @=@ dnet:dataCite_title @=@ subtitle @=@ subtitle +dnet:dataCite_title @=@ dnet:dataCite_title @=@ translated title @=@ translated title +datacite:relation_typologies @=@ datacite:relation_typologies @=@ IsCitedBy @=@ IsCitedBy +datacite:relation_typologies @=@ datacite:relation_typologies @=@ IsNewVersionOf @=@ IsNewVersionOf +datacite:relation_typologies @=@ datacite:relation_typologies @=@ IsPartOf @=@ IsPartOf +datacite:relation_typologies @=@ datacite:relation_typologies @=@ IsPreviousVersionOf @=@ IsPreviousVersionOf +datacite:relation_typologies @=@ datacite:relation_typologies @=@ IsReferencedBy @=@ IsReferencedBy +datacite:relation_typologies @=@ datacite:relation_typologies @=@ References @=@ References +datacite:relation_typologies @=@ datacite:relation_typologies @=@ UNKNOWN @=@ UNKNOWN +dnet:result_typologies @=@ dnet:result_typologies @=@ dataset @=@ dataset +dnet:result_typologies @=@ dnet:result_typologies @=@ other @=@ other +dnet:result_typologies @=@ dnet:result_typologies @=@ publication @=@ publication +dnet:result_typologies @=@ dnet:result_typologies @=@ software @=@ software +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ ERC-ADG @=@ Advanced Grant +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ BBI-CSA @=@ Bio-based Industries Coordination and Support action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ BBI-IA-DEMO @=@ Bio-based Industries Innovation action - Demonstration +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ BBI-IA-FLAG @=@ Bio-based Industries Innovation action - Flagship +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ BBI-RIA @=@ Bio-based Industries Research and Innovation action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ MSCA-IF-EF-CAR @=@ CAR – Career Restart panel +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ COFUND-EJP @=@ COFUND (European Joint Programme) +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ COFUND-PCP @=@ COFUND (PCP) +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ COFUND-PPI @=@ COFUND (PPI) +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ CS2-CSA @=@ CS2 Coordination and Support action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ CS2-IA @=@ CS2 Innovation Action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ CS2-RIA @=@ CS2 Research and Innovation action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ CSA-LS @=@ CSA Lump sum +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ ERC-COG @=@ Consolidator Grant +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ FCH2-CSA @=@ Coordination & support action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ CSA @=@ Coordination and support action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ MSCA-COFUND-DP @=@ Doctoral programmes +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ ECSEL-CSA @=@ ECSEL Coordination & Support action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ ECSEL-IA @=@ ECSEL Innovation Action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ ECSEL-RIA @=@ ECSEL Research and Innovation Actions +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ ERA-NET-Cofund @=@ ERA-NET Cofund +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ ERC-POC-LS @=@ ERC Proof of Concept Lump Sum Pilot +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ ERC-SyG @=@ ERC Synergy Grant +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ ERC-LVG @=@ ERC low value grant +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ H2020-EEN-SGA @=@ Enterprise Europe Network - Specific Grant Agreement +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ MSCA-ITN-EID @=@ European Industrial Doctorates +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ MSCA-ITN-EJD @=@ European Joint Doctorates +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ MSCA-ITN-ETN @=@ European Training Networks +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ FCH2-IA @=@ FCH2 Innovation action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ FCH2-RIA @=@ FCH2 Research and Innovation action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ MSCA-COFUND-FP @=@ Fellowship programmes +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ MSCA-IF-GF @=@ Global Fellowships +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ IMI2-CSA @=@ IMI2 Coordination & support action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ IMI2-RIA @=@ IMI2 Research and Innovation action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ Shift2Rail-IA-LS @=@ Innovation Action Lump-Sum +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ IA-LS @=@ Innovation Action Lump-Sum +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ IA @=@ Innovation action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ Shift2Rail-IA @=@ Innovation action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ PCP @=@ Pre-Commercial Procurement +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ ERC-POC @=@ Proof of Concept Grant +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ PPI @=@ Public Procurement of Innovative Solutions +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ MSCA-IF-EF-RI @=@ RI – Reintegration panel +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ MSCA-RISE @=@ RISE +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ Shift2Rail-RIA-LS @=@ Research and Innovation Action Lump-Sum +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ Shift2Rail-RIA @=@ Research and Innovation action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ RIA @=@ Research and Innovation action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ RIA-LS @=@ Research and Innovation action Lump Sum +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ SESAR-CSA @=@ SESAR: Coordination and Support Action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ SESAR-IA @=@ SESAR: Innovation action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ SESAR-RIA @=@ SESAR: Research and Innovation action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ SGA-RIA @=@ SGA Research and Innovation action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ SME-2b @=@ SME Instrument (grant only and blended finance) +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ SME-1 @=@ SME instrument phase 1 +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ SME-2 @=@ SME instrument phase 2 +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ Shift2Rail-CSA @=@ Shift2Rail - Coordination and Support action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ MSCA-IF-EF-SE @=@ Society and Enterprise panel +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ SGA-CSA @=@ Specific Grant agreement and Coordination and Support Action +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ MSCA-IF-EF-ST @=@ Standard EF +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ ERC-STG @=@ Starting Grant +ec:h2020toas @=@ Horizon 2020 - Type of Actions @=@ MSCA-SNLS @=@ Grant to identified beneficiary - Coordination and support actions (MSCA-Special Needs lump sum) +wt:contractTypes @=@ wt:contractTypes @=@ UNKNOWN @=@ UNKNOWN +dnet:countries @=@ dnet:countries @=@ AF @=@ Afghanistan +dnet:countries @=@ dnet:countries @=@ AL @=@ Albania +dnet:countries @=@ dnet:countries @=@ DZ @=@ Algeria +dnet:countries @=@ dnet:countries @=@ AS @=@ American Samoa +dnet:countries @=@ dnet:countries @=@ AD @=@ Andorra +dnet:countries @=@ dnet:countries @=@ AO @=@ Angola +dnet:countries @=@ dnet:countries @=@ AI @=@ Anguilla +dnet:countries @=@ dnet:countries @=@ AQ @=@ Antarctica +dnet:countries @=@ dnet:countries @=@ AG @=@ Antigua and Barbuda +dnet:countries @=@ dnet:countries @=@ AR @=@ Argentina +dnet:countries @=@ dnet:countries @=@ AM @=@ Armenia +dnet:countries @=@ dnet:countries @=@ AW @=@ Aruba +dnet:countries @=@ dnet:countries @=@ AU @=@ Australia +dnet:countries @=@ dnet:countries @=@ AT @=@ Austria +dnet:countries @=@ dnet:countries @=@ AZ @=@ Azerbaijan +dnet:countries @=@ dnet:countries @=@ BS @=@ Bahamas +dnet:countries @=@ dnet:countries @=@ BH @=@ Bahrain +dnet:countries @=@ dnet:countries @=@ BD @=@ Bangladesh +dnet:countries @=@ dnet:countries @=@ BB @=@ Barbados +dnet:countries @=@ dnet:countries @=@ BY @=@ Belarus +dnet:countries @=@ dnet:countries @=@ BE @=@ Belgium +dnet:countries @=@ dnet:countries @=@ BZ @=@ Belize +dnet:countries @=@ dnet:countries @=@ BJ @=@ Benin +dnet:countries @=@ dnet:countries @=@ BM @=@ Bermuda +dnet:countries @=@ dnet:countries @=@ BT @=@ Bhutan +dnet:countries @=@ dnet:countries @=@ BO @=@ Bolivia +dnet:countries @=@ dnet:countries @=@ BQ @=@ Bonaire, Sint Eustatius and Saba +dnet:countries @=@ dnet:countries @=@ BA @=@ Bosnia and Herzegovina +dnet:countries @=@ dnet:countries @=@ BW @=@ Botswana +dnet:countries @=@ dnet:countries @=@ BV @=@ Bouvet Island +dnet:countries @=@ dnet:countries @=@ BR @=@ Brazil +dnet:countries @=@ dnet:countries @=@ IO @=@ British Indian Ocean Territory +dnet:countries @=@ dnet:countries @=@ BN @=@ Brunei Darussalam +dnet:countries @=@ dnet:countries @=@ BG @=@ Bulgaria +dnet:countries @=@ dnet:countries @=@ BF @=@ Burkina Faso +dnet:countries @=@ dnet:countries @=@ BI @=@ Burundi +dnet:countries @=@ dnet:countries @=@ KH @=@ Cambodia +dnet:countries @=@ dnet:countries @=@ CM @=@ Cameroon +dnet:countries @=@ dnet:countries @=@ CA @=@ Canada +dnet:countries @=@ dnet:countries @=@ CV @=@ Cape Verde +dnet:countries @=@ dnet:countries @=@ KY @=@ Cayman Islands +dnet:countries @=@ dnet:countries @=@ CF @=@ Central African Republic +dnet:countries @=@ dnet:countries @=@ TD @=@ Chad +dnet:countries @=@ dnet:countries @=@ CL @=@ Chile +dnet:countries @=@ dnet:countries @=@ CN @=@ China (People's Republic of) +dnet:countries @=@ dnet:countries @=@ CX @=@ Christmas Island +dnet:countries @=@ dnet:countries @=@ CC @=@ Cocos (Keeling) Islands +dnet:countries @=@ dnet:countries @=@ CO @=@ Colombia +dnet:countries @=@ dnet:countries @=@ KM @=@ Comoros +dnet:countries @=@ dnet:countries @=@ CG @=@ Congo +dnet:countries @=@ dnet:countries @=@ CD @=@ Congo (Democratic Republic of) +dnet:countries @=@ dnet:countries @=@ CK @=@ Cook Islands +dnet:countries @=@ dnet:countries @=@ CR @=@ Costa Rica +dnet:countries @=@ dnet:countries @=@ CI @=@ Cote d'Ivoire +dnet:countries @=@ dnet:countries @=@ HR @=@ Croatia +dnet:countries @=@ dnet:countries @=@ CU @=@ Cuba +dnet:countries @=@ dnet:countries @=@ CW @=@ Curaçao +dnet:countries @=@ dnet:countries @=@ CY @=@ Cyprus +dnet:countries @=@ dnet:countries @=@ CZ @=@ Czech Republic +dnet:countries @=@ dnet:countries @=@ DK @=@ Denmark +dnet:countries @=@ dnet:countries @=@ DJ @=@ Djibouti +dnet:countries @=@ dnet:countries @=@ DM @=@ Dominica +dnet:countries @=@ dnet:countries @=@ DO @=@ Dominican Republic +dnet:countries @=@ dnet:countries @=@ EC @=@ Ecuador +dnet:countries @=@ dnet:countries @=@ EG @=@ Egypt +dnet:countries @=@ dnet:countries @=@ SV @=@ El Salvador +dnet:countries @=@ dnet:countries @=@ GQ @=@ Equatorial Guinea +dnet:countries @=@ dnet:countries @=@ ER @=@ Eritrea +dnet:countries @=@ dnet:countries @=@ EE @=@ Estonia +dnet:countries @=@ dnet:countries @=@ ET @=@ Ethiopia +dnet:countries @=@ dnet:countries @=@ EU @=@ European Union +dnet:countries @=@ dnet:countries @=@ FK @=@ Falkland Islands (Malvinas) +dnet:countries @=@ dnet:countries @=@ FO @=@ Faroe Islands +dnet:countries @=@ dnet:countries @=@ FJ @=@ Fiji +dnet:countries @=@ dnet:countries @=@ FI @=@ Finland +dnet:countries @=@ dnet:countries @=@ MK @=@ Former Yugoslav Republic of Macedonia +dnet:countries @=@ dnet:countries @=@ FR @=@ France +dnet:countries @=@ dnet:countries @=@ GF @=@ French Guiana +dnet:countries @=@ dnet:countries @=@ PF @=@ French Polynesia +dnet:countries @=@ dnet:countries @=@ TF @=@ French Southern Territories +dnet:countries @=@ dnet:countries @=@ GA @=@ Gabon +dnet:countries @=@ dnet:countries @=@ GM @=@ Gambia +dnet:countries @=@ dnet:countries @=@ GE @=@ Georgia +dnet:countries @=@ dnet:countries @=@ DE @=@ Germany +dnet:countries @=@ dnet:countries @=@ GH @=@ Ghana +dnet:countries @=@ dnet:countries @=@ GI @=@ Gibraltar +dnet:countries @=@ dnet:countries @=@ GR @=@ Greece +dnet:countries @=@ dnet:countries @=@ GL @=@ Greenland +dnet:countries @=@ dnet:countries @=@ GD @=@ Grenada +dnet:countries @=@ dnet:countries @=@ GP @=@ Guadeloupe +dnet:countries @=@ dnet:countries @=@ GU @=@ Guam +dnet:countries @=@ dnet:countries @=@ GT @=@ Guatemala +dnet:countries @=@ dnet:countries @=@ GG @=@ Guernsey +dnet:countries @=@ dnet:countries @=@ GN @=@ Guinea +dnet:countries @=@ dnet:countries @=@ GW @=@ Guinea-Bissau +dnet:countries @=@ dnet:countries @=@ GY @=@ Guyana +dnet:countries @=@ dnet:countries @=@ HT @=@ Haiti +dnet:countries @=@ dnet:countries @=@ HM @=@ Heard Island and McDonald Islands +dnet:countries @=@ dnet:countries @=@ VA @=@ Holy See (Vatican City State) +dnet:countries @=@ dnet:countries @=@ HN @=@ Honduras +dnet:countries @=@ dnet:countries @=@ HK @=@ Hong Kong +dnet:countries @=@ dnet:countries @=@ HU @=@ Hungary +dnet:countries @=@ dnet:countries @=@ IS @=@ Iceland +dnet:countries @=@ dnet:countries @=@ IN @=@ India +dnet:countries @=@ dnet:countries @=@ ID @=@ Indonesia +dnet:countries @=@ dnet:countries @=@ IR @=@ Iran (Islamic Republic of) +dnet:countries @=@ dnet:countries @=@ IQ @=@ Iraq +dnet:countries @=@ dnet:countries @=@ IE @=@ Ireland +dnet:countries @=@ dnet:countries @=@ IM @=@ Isle of Man +dnet:countries @=@ dnet:countries @=@ IL @=@ Israel +dnet:countries @=@ dnet:countries @=@ IT @=@ Italy +dnet:countries @=@ dnet:countries @=@ JM @=@ Jamaica +dnet:countries @=@ dnet:countries @=@ JP @=@ Japan +dnet:countries @=@ dnet:countries @=@ JE @=@ Jersey +dnet:countries @=@ dnet:countries @=@ JO @=@ Jordan +dnet:countries @=@ dnet:countries @=@ KZ @=@ Kazakhstan +dnet:countries @=@ dnet:countries @=@ KE @=@ Kenya +dnet:countries @=@ dnet:countries @=@ KI @=@ Kiribati +dnet:countries @=@ dnet:countries @=@ KR @=@ Korea (Republic of) +dnet:countries @=@ dnet:countries @=@ KP @=@ Korea, Democatric People's Republic of +dnet:countries @=@ dnet:countries @=@ XK @=@ Kosovo * UN resolution +dnet:countries @=@ dnet:countries @=@ KW @=@ Kuwait +dnet:countries @=@ dnet:countries @=@ KG @=@ Kyrgyzstan +dnet:countries @=@ dnet:countries @=@ LA @=@ Lao (People's Democratic Republic) +dnet:countries @=@ dnet:countries @=@ LV @=@ Latvia +dnet:countries @=@ dnet:countries @=@ LB @=@ Lebanon +dnet:countries @=@ dnet:countries @=@ LS @=@ Lesotho +dnet:countries @=@ dnet:countries @=@ LR @=@ Liberia +dnet:countries @=@ dnet:countries @=@ LY @=@ Libyan Arab Jamahiriya +dnet:countries @=@ dnet:countries @=@ LI @=@ Liechtenstein +dnet:countries @=@ dnet:countries @=@ LT @=@ Lithuania +dnet:countries @=@ dnet:countries @=@ LU @=@ Luxembourg +dnet:countries @=@ dnet:countries @=@ MO @=@ Macao +dnet:countries @=@ dnet:countries @=@ MG @=@ Madagascar +dnet:countries @=@ dnet:countries @=@ MW @=@ Malawi +dnet:countries @=@ dnet:countries @=@ MY @=@ Malaysia +dnet:countries @=@ dnet:countries @=@ MV @=@ Maldives +dnet:countries @=@ dnet:countries @=@ ML @=@ Mali +dnet:countries @=@ dnet:countries @=@ MT @=@ Malta +dnet:countries @=@ dnet:countries @=@ MH @=@ Marshall Islands +dnet:countries @=@ dnet:countries @=@ MQ @=@ Martinique +dnet:countries @=@ dnet:countries @=@ MR @=@ Mauritania +dnet:countries @=@ dnet:countries @=@ MU @=@ Mauritius +dnet:countries @=@ dnet:countries @=@ YT @=@ Mayotte +dnet:countries @=@ dnet:countries @=@ MX @=@ Mexico +dnet:countries @=@ dnet:countries @=@ FM @=@ Micronesia, Federated States of +dnet:countries @=@ dnet:countries @=@ MD @=@ Moldova (Republic of) +dnet:countries @=@ dnet:countries @=@ MN @=@ Mongolia +dnet:countries @=@ dnet:countries @=@ ME @=@ Montenegro +dnet:countries @=@ dnet:countries @=@ MS @=@ Montserrat +dnet:countries @=@ dnet:countries @=@ MA @=@ Morocco +dnet:countries @=@ dnet:countries @=@ MZ @=@ Mozambique +dnet:countries @=@ dnet:countries @=@ MM @=@ Myanmar +dnet:countries @=@ dnet:countries @=@ NA @=@ Namibia +dnet:countries @=@ dnet:countries @=@ NR @=@ Nauru +dnet:countries @=@ dnet:countries @=@ NP @=@ Nepal +dnet:countries @=@ dnet:countries @=@ NL @=@ Netherlands +dnet:countries @=@ dnet:countries @=@ AN @=@ Netherlands Antilles +dnet:countries @=@ dnet:countries @=@ NC @=@ New Caledonia +dnet:countries @=@ dnet:countries @=@ NZ @=@ New Zealand +dnet:countries @=@ dnet:countries @=@ NI @=@ Nicaragua +dnet:countries @=@ dnet:countries @=@ NE @=@ Niger +dnet:countries @=@ dnet:countries @=@ NG @=@ Nigeria +dnet:countries @=@ dnet:countries @=@ NU @=@ Niue +dnet:countries @=@ dnet:countries @=@ NF @=@ Norfolk Island +dnet:countries @=@ dnet:countries @=@ MP @=@ Northern Mariana Islands +dnet:countries @=@ dnet:countries @=@ NO @=@ Norway +dnet:countries @=@ dnet:countries @=@ OC @=@ Oceania +dnet:countries @=@ dnet:countries @=@ OM @=@ Oman +dnet:countries @=@ dnet:countries @=@ PK @=@ Pakistan +dnet:countries @=@ dnet:countries @=@ PW @=@ Palau +dnet:countries @=@ dnet:countries @=@ PS @=@ Palestinian-administered areas +dnet:countries @=@ dnet:countries @=@ PA @=@ Panama +dnet:countries @=@ dnet:countries @=@ PG @=@ Papua New Guinea +dnet:countries @=@ dnet:countries @=@ PY @=@ Paraguay +dnet:countries @=@ dnet:countries @=@ PE @=@ Peru +dnet:countries @=@ dnet:countries @=@ PH @=@ Philippines +dnet:countries @=@ dnet:countries @=@ PN @=@ Pitcairn +dnet:countries @=@ dnet:countries @=@ PL @=@ Poland +dnet:countries @=@ dnet:countries @=@ PT @=@ Portugal +dnet:countries @=@ dnet:countries @=@ PR @=@ Puerto Rico +dnet:countries @=@ dnet:countries @=@ QA @=@ Qatar +dnet:countries @=@ dnet:countries @=@ RO @=@ Romania +dnet:countries @=@ dnet:countries @=@ RU @=@ Russian Federation +dnet:countries @=@ dnet:countries @=@ RW @=@ Rwanda +dnet:countries @=@ dnet:countries @=@ RE @=@ Réunion +dnet:countries @=@ dnet:countries @=@ SH @=@ Saint Helena, Ascension and Tristan da Cunha +dnet:countries @=@ dnet:countries @=@ KN @=@ Saint Kitts and Nevis +dnet:countries @=@ dnet:countries @=@ LC @=@ Saint Lucia +dnet:countries @=@ dnet:countries @=@ MF @=@ Saint Martin (French Part) +dnet:countries @=@ dnet:countries @=@ PM @=@ Saint Pierre and Miquelon +dnet:countries @=@ dnet:countries @=@ VC @=@ Saint Vincent and the Grenadines +dnet:countries @=@ dnet:countries @=@ BL @=@ Saint-Barthélemy +dnet:countries @=@ dnet:countries @=@ WS @=@ Samoa +dnet:countries @=@ dnet:countries @=@ SM @=@ San Marino +dnet:countries @=@ dnet:countries @=@ SA @=@ Saudi Arabia +dnet:countries @=@ dnet:countries @=@ SN @=@ Senegal +dnet:countries @=@ dnet:countries @=@ RS @=@ Serbia +dnet:countries @=@ dnet:countries @=@ CS @=@ Serbia and Montenegro +dnet:countries @=@ dnet:countries @=@ SC @=@ Seychelles +dnet:countries @=@ dnet:countries @=@ SL @=@ Sierra Leone +dnet:countries @=@ dnet:countries @=@ SG @=@ Singapore +dnet:countries @=@ dnet:countries @=@ SX @=@ Sint Maarten (Dutch Part) +dnet:countries @=@ dnet:countries @=@ SK @=@ Slovakia +dnet:countries @=@ dnet:countries @=@ SI @=@ Slovenia +dnet:countries @=@ dnet:countries @=@ SB @=@ Solomon Islands +dnet:countries @=@ dnet:countries @=@ SO @=@ Somalia +dnet:countries @=@ dnet:countries @=@ ZA @=@ South Africa +dnet:countries @=@ dnet:countries @=@ GS @=@ South Georgia and the South Sandwich Islands +dnet:countries @=@ dnet:countries @=@ SS @=@ South Sudan +dnet:countries @=@ dnet:countries @=@ ES @=@ Spain +dnet:countries @=@ dnet:countries @=@ LK @=@ Sri Lanka +dnet:countries @=@ dnet:countries @=@ SD @=@ Sudan +dnet:countries @=@ dnet:countries @=@ SR @=@ Suriname +dnet:countries @=@ dnet:countries @=@ SJ @=@ Svalbard and Jan Mayen +dnet:countries @=@ dnet:countries @=@ SZ @=@ Swaziland +dnet:countries @=@ dnet:countries @=@ SE @=@ Sweden +dnet:countries @=@ dnet:countries @=@ CH @=@ Switzerland +dnet:countries @=@ dnet:countries @=@ SY @=@ Syrian Arab Republic +dnet:countries @=@ dnet:countries @=@ ST @=@ São Tomé and Príncipe +dnet:countries @=@ dnet:countries @=@ TW @=@ Taiwan +dnet:countries @=@ dnet:countries @=@ TJ @=@ Tajikistan +dnet:countries @=@ dnet:countries @=@ TZ @=@ Tanzania (United Republic of) +dnet:countries @=@ dnet:countries @=@ TH @=@ Thailand +dnet:countries @=@ dnet:countries @=@ TL @=@ Timor-Leste +dnet:countries @=@ dnet:countries @=@ TG @=@ Togo +dnet:countries @=@ dnet:countries @=@ TK @=@ Tokelau +dnet:countries @=@ dnet:countries @=@ TO @=@ Tonga +dnet:countries @=@ dnet:countries @=@ TT @=@ Trinidad and Tobago +dnet:countries @=@ dnet:countries @=@ TN @=@ Tunisia +dnet:countries @=@ dnet:countries @=@ TR @=@ Turkey +dnet:countries @=@ dnet:countries @=@ TM @=@ Turkmenistan +dnet:countries @=@ dnet:countries @=@ TC @=@ Turks and Caicos Islands +dnet:countries @=@ dnet:countries @=@ TV @=@ Tuvalu +dnet:countries @=@ dnet:countries @=@ UNKNOWN @=@ UNKNOWN +dnet:countries @=@ dnet:countries @=@ UG @=@ Uganda +dnet:countries @=@ dnet:countries @=@ UA @=@ Ukraine +dnet:countries @=@ dnet:countries @=@ AE @=@ United Arab Emirates +dnet:countries @=@ dnet:countries @=@ GB @=@ United Kingdom +dnet:countries @=@ dnet:countries @=@ US @=@ United States +dnet:countries @=@ dnet:countries @=@ UM @=@ United States Minor Outlying Islands +dnet:countries @=@ dnet:countries @=@ UY @=@ Uruguay +dnet:countries @=@ dnet:countries @=@ UZ @=@ Uzbekistan +dnet:countries @=@ dnet:countries @=@ VU @=@ Vanuatu +dnet:countries @=@ dnet:countries @=@ VE @=@ Venezuela +dnet:countries @=@ dnet:countries @=@ VN @=@ Viet Nam +dnet:countries @=@ dnet:countries @=@ VG @=@ Virgin Islands (British) +dnet:countries @=@ dnet:countries @=@ VI @=@ Virgin Islands, U.S. +dnet:countries @=@ dnet:countries @=@ WF @=@ Wallis and Futuna +dnet:countries @=@ dnet:countries @=@ EH @=@ Western Sahara +dnet:countries @=@ dnet:countries @=@ YE @=@ Yemen +dnet:countries @=@ dnet:countries @=@ YU @=@ Yugoslavia +dnet:countries @=@ dnet:countries @=@ ZM @=@ Zambia +dnet:countries @=@ dnet:countries @=@ ZW @=@ Zimbabwe +dnet:countries @=@ dnet:countries @=@ AX @=@ Åland Islands +dnet:datasourceCompatibilityLevel @=@ dnet:datasourceCompatibilityLevel @=@ openaire2.0 @=@ OpenAIRE 2.0 (EC funding) +dnet:datasourceCompatibilityLevel @=@ dnet:datasourceCompatibilityLevel @=@ driver-openaire2.0 @=@ OpenAIRE 2.0+ (DRIVER OA, EC funding) +dnet:datasourceCompatibilityLevel @=@ dnet:datasourceCompatibilityLevel @=@ openaire3.0 @=@ OpenAIRE 3.0 (OA, funding) +dnet:datasourceCompatibilityLevel @=@ dnet:datasourceCompatibilityLevel @=@ openaire4.0 @=@ OpenAIRE 4.0 (inst.&thematic. repo.) +dnet:datasourceCompatibilityLevel @=@ dnet:datasourceCompatibilityLevel @=@ driver @=@ OpenAIRE Basic (DRIVER OA) +dnet:datasourceCompatibilityLevel @=@ dnet:datasourceCompatibilityLevel @=@ openaire2.0_data @=@ OpenAIRE Data (funded, referenced datasets) +dnet:datasourceCompatibilityLevel @=@ dnet:datasourceCompatibilityLevel @=@ hostedBy @=@ collected from a compatible aggregator +dnet:datasourceCompatibilityLevel @=@ dnet:datasourceCompatibilityLevel @=@ UNKNOWN @=@ not available +dnet:datasourceCompatibilityLevel @=@ dnet:datasourceCompatibilityLevel @=@ native @=@ proprietary +dnet:datasourceCompatibilityLevel @=@ dnet:datasourceCompatibilityLevel @=@ notCompatible @=@ under validation +dnet:datasourceCompatibilityLevel @=@ dnet:datasourceCompatibilityLevel @=@ openaire-cris_1.1 @=@ OpenAIRE CRIS v1.1 +fct:funding_relations @=@ fct:funding_relations @=@ fct:hasParentFunding @=@ fct:hasParentFunding +dnet:protocols @=@ dnet:protocols @=@ HTTPWithFileName @=@ HTTPWithFileName +dnet:protocols @=@ dnet:protocols @=@ NetCDF @=@ NetCDF +dnet:protocols @=@ dnet:protocols @=@ OpenDAP @=@ OpenDAP +dnet:protocols @=@ dnet:protocols @=@ schemaorg @=@ Schema.org +dnet:protocols @=@ dnet:protocols @=@ UNKNOWN @=@ UNKNOWN +dnet:protocols @=@ dnet:protocols @=@ api @=@ api +dnet:protocols @=@ dnet:protocols @=@ dataciteESPlugins @=@ dataciteESPlugins +dnet:protocols @=@ dnet:protocols @=@ datasetsbyjournal @=@ datasetsbyjournal +dnet:protocols @=@ dnet:protocols @=@ datasetsbyproject @=@ datasetsbyproject +dnet:protocols @=@ dnet:protocols @=@ excelFile @=@ excelFile +dnet:protocols @=@ dnet:protocols @=@ file @=@ file +dnet:protocols @=@ dnet:protocols @=@ fileGzip @=@ fileGzip +dnet:protocols @=@ dnet:protocols @=@ files_by_rpc @=@ files_by_rpc +dnet:protocols @=@ dnet:protocols @=@ files_from_mdstore @=@ files_from_mdstore +dnet:protocols @=@ dnet:protocols @=@ files_from_metadata @=@ files_from_metadata +dnet:protocols @=@ dnet:protocols @=@ filesystem @=@ filesystem +dnet:protocols @=@ dnet:protocols @=@ ftp @=@ ftp +dnet:protocols @=@ dnet:protocols @=@ gristProjects @=@ gristProjects +dnet:protocols @=@ dnet:protocols @=@ gtr2Projects @=@ gtr2Projects +dnet:protocols @=@ dnet:protocols @=@ http @=@ http +dnet:protocols @=@ dnet:protocols @=@ httpCSV @=@ httpCSV +dnet:protocols @=@ dnet:protocols @=@ httpList @=@ httpList +dnet:protocols @=@ dnet:protocols @=@ jdbc @=@ jdbc +dnet:protocols @=@ dnet:protocols @=@ oai @=@ oai +dnet:protocols @=@ dnet:protocols @=@ oai_sets @=@ oai_sets +dnet:protocols @=@ dnet:protocols @=@ other @=@ other +dnet:protocols @=@ dnet:protocols @=@ re3data @=@ re3data +dnet:protocols @=@ dnet:protocols @=@ rest @=@ rest +dnet:protocols @=@ dnet:protocols @=@ rest_json2xml @=@ rest_json2xml +dnet:protocols @=@ dnet:protocols @=@ sftp @=@ sftp +dnet:protocols @=@ dnet:protocols @=@ soap @=@ soap +dnet:protocols @=@ dnet:protocols @=@ sparql @=@ sparql +dnet:protocols @=@ dnet:protocols @=@ sword @=@ sword +dnet:protocols @=@ dnet:protocols @=@ targz @=@ targz +dnet:protocols @=@ dnet:protocols @=@ remoteMdstore @=@ remoteMdstore +wt:funding_typologies @=@ Wellcome Trust: Funding Typologies @=@ wt:fundingStream @=@ Wellcome Trust: Funding Stream +dnet:externalReference_typologies @=@ dnet:externalReference_typologies @=@ accessionNumber @=@ accessionNumber +dnet:externalReference_typologies @=@ dnet:externalReference_typologies @=@ dataset @=@ dataset +dnet:externalReference_typologies @=@ dnet:externalReference_typologies @=@ software @=@ software +datacite:id_typologies @=@ datacite:id_typologies @=@ ARK @=@ ARK +datacite:id_typologies @=@ datacite:id_typologies @=@ DOI @=@ DOI +datacite:id_typologies @=@ datacite:id_typologies @=@ EAN13 @=@ EAN13 +datacite:id_typologies @=@ datacite:id_typologies @=@ EISSN @=@ EISSN +datacite:id_typologies @=@ datacite:id_typologies @=@ Handle @=@ Handle +datacite:id_typologies @=@ datacite:id_typologies @=@ ISBN @=@ ISBN +datacite:id_typologies @=@ datacite:id_typologies @=@ ISSN @=@ ISSN +datacite:id_typologies @=@ datacite:id_typologies @=@ ISTC @=@ ISTC +datacite:id_typologies @=@ datacite:id_typologies @=@ LISSN @=@ LISSN +datacite:id_typologies @=@ datacite:id_typologies @=@ LSID @=@ LSID +datacite:id_typologies @=@ datacite:id_typologies @=@ PURL @=@ PURL +datacite:id_typologies @=@ datacite:id_typologies @=@ UNKNOWN @=@ UNKNOWN +datacite:id_typologies @=@ datacite:id_typologies @=@ UPC @=@ UPC +datacite:id_typologies @=@ datacite:id_typologies @=@ URL @=@ URL +datacite:id_typologies @=@ datacite:id_typologies @=@ URN @=@ URN +dnet:pid_types @=@ dnet:pid_types @=@ actrn @=@ ACTRN Identifier +dnet:pid_types @=@ dnet:pid_types @=@ nct @=@ ClinicalTrials.gov Identifier +dnet:pid_types @=@ dnet:pid_types @=@ euctr @=@ EU Clinical Trials Register +dnet:pid_types @=@ dnet:pid_types @=@ epo_id @=@ European Patent Office application ID +dnet:pid_types @=@ dnet:pid_types @=@ gsk @=@ GSK Identifier +dnet:pid_types @=@ dnet:pid_types @=@ GeoPass @=@ Geographic Location-Password Scheme +dnet:pid_types @=@ dnet:pid_types @=@ GBIF @=@ Global Biodiversity Information Facility +dnet:pid_types @=@ dnet:pid_types @=@ isrctn @=@ ISRCTN Identifier +dnet:pid_types @=@ dnet:pid_types @=@ ISNI @=@ International Standard Name Identifier +dnet:pid_types @=@ dnet:pid_types @=@ jprn @=@ JPRN Identifier +dnet:pid_types @=@ dnet:pid_types @=@ mag_id @=@ Microsoft Academic Graph Identifier +dnet:pid_types @=@ dnet:pid_types @=@ oai @=@ Open Archives Initiative +dnet:pid_types @=@ dnet:pid_types @=@ orcid @=@ Open Researcher and Contributor ID +dnet:pid_types @=@ dnet:pid_types @=@ PANGAEA @=@ PANGAEA +dnet:pid_types @=@ dnet:pid_types @=@ epo_nr_epodoc @=@ Patent application number in EPODOC format +dnet:pid_types @=@ dnet:pid_types @=@ UNKNOWN @=@ UNKNOWN +dnet:pid_types @=@ dnet:pid_types @=@ VIAF @=@ Virtual International Authority File +dnet:pid_types @=@ dnet:pid_types @=@ arXiv @=@ arXiv +dnet:pid_types @=@ dnet:pid_types @=@ doi @=@ doi +dnet:pid_types @=@ dnet:pid_types @=@ grid @=@ grid +dnet:pid_types @=@ dnet:pid_types @=@ info:eu-repo/dai @=@ info:eu-repo/dai +dnet:pid_types @=@ dnet:pid_types @=@ orcidworkid @=@ orcid workid +dnet:pid_types @=@ dnet:pid_types @=@ pmc @=@ pmc +dnet:pid_types @=@ dnet:pid_types @=@ pmid @=@ pmid +dnet:pid_types @=@ dnet:pid_types @=@ urn @=@ urn +dnet:pid_types @=@ dnet:pid_types @=@ who @=@ WHO Identifier +dnet:pid_types @=@ dnet:pid_types @=@ drks @=@ DRKS Identifier +dnet:pid_types @=@ dnet:pid_types @=@ handle @=@ Handle +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/SUBJECT/ACM @=@ An ACM classification term that can be associated to your publications +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/SUBJECT/ARXIV @=@ An ARXIV classification term that can be associated to your publications +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/SUBJECT/DDC @=@ A Dewey Decimal classification term (DDC) that can be associated to your publications +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/SUBJECT/JEL @=@ A Journal of Economic Literature (JEL) classification term that can be associated to your publications +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/OPENACCESS_VERSION @=@ An Open Access versions of your publications +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/DATASET/IS_REFERENCED_BY @=@ A dataset referenced by your records +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/DATASET/REFERENCES @=@ A dataset that refers to your records +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/DATASET/IS_RELATED_TO @=@ A dataset related to your records +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/DATASET/IS_SUPPLEMENTED_TO @=@ A dataset that supplements your records +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/PUBLICATION/IS_RELATED_TO @=@ A publication related to your records +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/PUBLICATION/REFERENCES @=@ A publication referenced by your records +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/PUBLICATION/IS_REFERENCED_BY @=@ A publication that refers to your records +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/PUBLICATION/IS_SUPPLEMENTED_BY @=@ A publication that is supplemented by your records +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/PUBLICATION/IS_SUPPLEMENTED_TO @=@ A publication that supplements your records +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/SOFTWARE @=@ A software referred by your records +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MORE/OPENACCESS_VERSION @=@ Another Open Access version of a publication +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MORE/PID @=@ Another persistent identifier associated to your publications +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/SUBJECT/MESHEUROPMC @=@ A classification term from the Medical Subject Headings (MeSH) that can be associated to your publications +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/ABSTRACT @=@ An abstract describing among your publications +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/PUBLICATION_DATE @=@ A date of publication missing in your content +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/PID @=@ A persistent identifier associated to your publications +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MORE/SUBJECT/ACM @=@ Another ACM classification term that can be associated to your publications +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MORE/SUBJECT/ARXIV @=@ Another ARXIV classification term that can be associated to your publications +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MORE/SUBJECT/DDC @=@ Another Dewey Decimal classification term (DDC) that can be associated to your publications +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MORE/SUBJECT/JEL @=@ Another Journal of Economic Literature (JEL) classification term that can be associated to your publications +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MORE/SUBJECT/MESHEUROPMC @=@ Another classification term from the Medical Subject Headings (MeSH) that can be associated to your publications +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/PROJECT @=@ A project reference that can be associated to your publications +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/DATASET/IS_SUPPLEMENTED_BY @=@ A dataset that is supplemented by your records +dnet:topic_types @=@ dnet:topic_types @=@ ENRICH/MISSING/AUTHOR/ORCID @=@ An Open Researcher and Contributor ID (ORCID) that can be associated to an author of your publications +dnet:review_levels @=@ dnet:review_levels @=@ 0000 @=@ Unknown +dnet:review_levels @=@ dnet:review_levels @=@ 0002 @=@ nonPeerReviewed +dnet:review_levels @=@ dnet:review_levels @=@ 0001 @=@ peerReviewed \ No newline at end of file From 38f2508c87d9c748bc753c9373cb55903214263b Mon Sep 17 00:00:00 2001 From: "michele.artini" Date: Thu, 28 Jan 2021 08:24:45 +0100 Subject: [PATCH 068/445] new fields in mdstore beans --- .../mdstore/manager/common/model/MDStore.java | 45 +++++++++++++++++-- .../manager/common/model/MDStoreVersion.java | 33 ++++++++++---- .../manager/common/model/MDStoreWithInfo.java | 23 ++++++++++ 3 files changed, 88 insertions(+), 13 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStore.java b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStore.java index 68fc024af..345500737 100644 --- a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStore.java +++ b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStore.java @@ -2,12 +2,15 @@ package eu.dnetlib.data.mdstore.manager.common.model; import java.io.Serializable; +import java.util.Date; import java.util.UUID; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; @Entity @Table(name = "mdstores") @@ -38,6 +41,13 @@ public class MDStore implements Serializable { @Column(name = "api_id") private String apiId; + @Column(name = "hdfs_path") + private String hdfsPath; + + @Column(name = "creation_date") + @Temporal(TemporalType.TIMESTAMP) + private Date creationDate; + public String getId() { return id; } @@ -94,9 +104,28 @@ public class MDStore implements Serializable { this.apiId = apiId; } + public String getHdfsPath() { + return hdfsPath; + } + + public void setHdfsPath(final String hdfsPath) { + this.hdfsPath = hdfsPath; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(final Date creationDate) { + this.creationDate = creationDate; + } + public static MDStore newInstance( - final String format, final String layout, final String interpretation) { - return newInstance(format, layout, interpretation, null, null, null); + final String format, + final String layout, + final String interpretation, + final String hdfsBasePath) { + return newInstance(format, layout, interpretation, null, null, null, hdfsBasePath); } public static MDStore newInstance( @@ -105,15 +134,23 @@ public class MDStore implements Serializable { final String interpretation, final String dsName, final String dsId, - final String apiId) { + final String apiId, + final String hdfsBasePath) { + + final String mdId = "md-" + UUID.randomUUID(); + final MDStore md = new MDStore(); - md.setId("md-" + UUID.randomUUID()); + md.setId(mdId); md.setFormat(format); md.setLayout(layout); md.setInterpretation(interpretation); + md.setCreationDate(new Date()); md.setDatasourceName(dsName); md.setDatasourceId(dsId); md.setApiId(apiId); + md.setHdfsPath(String.format("%s/%s", hdfsBasePath, mdId)); + return md; } + } diff --git a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreVersion.java b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreVersion.java index 7ef24f191..62370c0f5 100644 --- a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreVersion.java +++ b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreVersion.java @@ -38,15 +38,22 @@ public class MDStoreVersion implements Serializable { @Column(name = "size") private long size = 0; - public static MDStoreVersion newInstance(final String mdId, final boolean writing) { - final MDStoreVersion t = new MDStoreVersion(); - t.setId(mdId + "-" + new Date().getTime()); - t.setMdstore(mdId); - t.setLastUpdate(null); - t.setWriting(writing); - t.setReadCount(0); - t.setSize(0); - return t; + @Column(name = "hdfs_path") + private String hdfsPath; + + public static MDStoreVersion newInstance(final String mdId, final boolean writing, final String hdfsBasePath) { + final MDStoreVersion v = new MDStoreVersion(); + + final String versionId = mdId + "-" + new Date().getTime(); + v.setId(versionId); + v.setMdstore(mdId); + v.setLastUpdate(null); + v.setWriting(writing); + v.setReadCount(0); + v.setSize(0); + v.setHdfsPath(String.format("%s/%s/%s", hdfsBasePath, mdId, versionId)); + + return v; } public String getId() { @@ -96,4 +103,12 @@ public class MDStoreVersion implements Serializable { public void setSize(final long size) { this.size = size; } + + public String getHdfsPath() { + return hdfsPath; + } + + public void setHdfsPath(final String hdfsPath) { + this.hdfsPath = hdfsPath; + } } diff --git a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreWithInfo.java b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreWithInfo.java index 438359241..72915a9c8 100644 --- a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreWithInfo.java +++ b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreWithInfo.java @@ -43,6 +43,10 @@ public class MDStoreWithInfo implements Serializable { @Column(name = "current_version") private String currentVersion; + @Column(name = "creation_date") + @Temporal(TemporalType.TIMESTAMP) + private Date creationDate; + @Column(name = "lastupdate") @Temporal(TemporalType.TIMESTAMP) private Date lastUpdate; @@ -53,6 +57,9 @@ public class MDStoreWithInfo implements Serializable { @Column(name = "n_versions") private long numberOfVersions = 0; + @Column(name = "hdfs_path") + private String hdfsPath; + public String getId() { return id; } @@ -117,6 +124,14 @@ public class MDStoreWithInfo implements Serializable { this.currentVersion = currentVersion; } + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(final Date creationDate) { + this.creationDate = creationDate; + } + public Date getLastUpdate() { return lastUpdate; } @@ -140,4 +155,12 @@ public class MDStoreWithInfo implements Serializable { public void setNumberOfVersions(final long numberOfVersions) { this.numberOfVersions = numberOfVersions; } + + public String getHdfsPath() { + return hdfsPath; + } + + public void setHdfsPath(final String hdfsPath) { + this.hdfsPath = hdfsPath; + } } From 98b9498b5745d1129ed665c2f22a83db595c478a Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Thu, 28 Jan 2021 09:51:17 +0100 Subject: [PATCH 069/445] Removed old messaging system not quite used from collection and Transformation workflow code refactor --- .../actionmanager/project/utils/ReadCSV.java | 2 +- .../project/utils/ReadExcel.java | 3 +- .../common/AggregationCounter.java | 60 ++++---- .../GenerateNativeStoreSparkJob.java | 133 ++++++----------- .../collection/plugin/CollectorPlugin.java | 4 +- .../plugin/oai/OaiCollectorPlugin.java | 12 +- .../collection/plugin/oai/OaiIterator.java | 20 +-- ...Exception.java => CollectorException.java} | 12 +- .../collection/worker/CollectorWorker.java | 93 ++++++++++++ .../worker/CollectorWorkerApplication.java | 55 +++++++ .../worker/DnetCollectorWorker.java | 139 ------------------ .../DnetCollectorWorkerApplication.java | 49 ------ .../worker/utils/CollectorPluginFactory.java | 8 +- .../worker/utils/HttpConnector.java | 24 +-- .../DnetTransformationException.java | 39 ++--- .../transformation/TransformSparkJobNode.java | 33 ++--- .../transformation/TransformationFactory.java | 87 ++++++----- .../dhp/transformation/xslt/Cleaner.java | 3 +- .../xslt/XSLTTransformationFunction.java | 102 ++++++------- .../collection_input_parameters.json | 36 ----- .../dhp/collection/collector_parameter.json | 6 + .../collection/oozie_app/config-default.xml | 18 +++ .../dhp/collection/oozie_app/workflow.xml | 77 ++++------ .../collector/worker/collector_parameter.json | 12 -- .../oozie_app/config-default.xml | 18 +++ .../dhp/transformation/oozie_app/workflow.xml | 69 ++++----- .../transformation_input_parameters.json | 22 +-- .../project/EXCELParserTest.java | 7 +- .../httpconnector/HttpConnectorTest.java | 9 +- .../DnetCollectorWorkerApplicationTests.java | 49 ++---- .../transformation/TransformationJobTest.java | 86 +++++++---- .../dhp/oa/graph/clean/CleaningFunctions.java | 24 +-- .../raw/MigrateDbEntitiesApplication.java | 2 +- .../raw/GenerateEntitiesApplicationTest.java | 2 +- .../dnetlib/dhp/oa/graph/raw/MappersTest.java | 2 +- 35 files changed, 597 insertions(+), 720 deletions(-) rename dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/{DnetCollectorException.java => CollectorException.java} (56%) create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java delete mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/DnetCollectorWorker.java delete mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/DnetCollectorWorkerApplication.java create mode 100644 dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_parameter.json create mode 100644 dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/config-default.xml delete mode 100644 dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collector/worker/collector_parameter.json create mode 100644 dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/config-default.xml diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java index dc6f46771..ca1c10611 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java @@ -17,8 +17,8 @@ import org.apache.hadoop.fs.Path; import com.fasterxml.jackson.databind.ObjectMapper; -import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; /** * Applies the parsing of a csv file and writes the Serialization of it in hdfs diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java index e665bc704..585a408f3 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java @@ -4,7 +4,6 @@ package eu.dnetlib.dhp.actionmanager.project.utils; import java.io.*; import java.nio.charset.StandardCharsets; -import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -15,8 +14,8 @@ import org.apache.hadoop.fs.Path; import com.fasterxml.jackson.databind.ObjectMapper; - import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; /** * Applies the parsing of an excel file and writes the Serialization of it in hdfs diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationCounter.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationCounter.java index 1ac2cb54b..bf2fd22cb 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationCounter.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationCounter.java @@ -1,45 +1,45 @@ -package eu.dnetlib.dhp.aggregation.common; -import org.apache.spark.util.LongAccumulator; +package eu.dnetlib.dhp.aggregation.common; import java.io.Serializable; +import org.apache.spark.util.LongAccumulator; public class AggregationCounter implements Serializable { - private LongAccumulator totalItems; - private LongAccumulator errorItems; - private LongAccumulator processedItems; + private LongAccumulator totalItems; + private LongAccumulator errorItems; + private LongAccumulator processedItems; - public AggregationCounter() { - } + public AggregationCounter() { + } - public AggregationCounter(LongAccumulator totalItems, LongAccumulator errorItems, LongAccumulator processedItems) { - this.totalItems = totalItems; - this.errorItems = errorItems; - this.processedItems = processedItems; - } + public AggregationCounter(LongAccumulator totalItems, LongAccumulator errorItems, LongAccumulator processedItems) { + this.totalItems = totalItems; + this.errorItems = errorItems; + this.processedItems = processedItems; + } - public LongAccumulator getTotalItems() { - return totalItems; - } + public LongAccumulator getTotalItems() { + return totalItems; + } - public void setTotalItems(LongAccumulator totalItems) { - this.totalItems = totalItems; - } + public void setTotalItems(LongAccumulator totalItems) { + this.totalItems = totalItems; + } - public LongAccumulator getErrorItems() { - return errorItems; - } + public LongAccumulator getErrorItems() { + return errorItems; + } - public void setErrorItems(LongAccumulator errorItems) { - this.errorItems = errorItems; - } + public void setErrorItems(LongAccumulator errorItems) { + this.errorItems = errorItems; + } - public LongAccumulator getProcessedItems() { - return processedItems; - } + public LongAccumulator getProcessedItems() { + return processedItems; + } - public void setProcessedItems(LongAccumulator processedItems) { - this.processedItems = processedItems; - } + public void setProcessedItems(LongAccumulator processedItems) { + this.processedItems = processedItems; + } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java index c0bd4c940..c9c29b4ea 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java @@ -5,12 +5,9 @@ import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; import java.util.Optional; -import org.apache.commons.cli.*; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.io.IntWritable; @@ -22,7 +19,6 @@ import org.apache.spark.api.java.JavaSparkContext; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoder; import org.apache.spark.sql.Encoders; -import org.apache.spark.sql.SparkSession; import org.apache.spark.util.LongAccumulator; import org.dom4j.Document; import org.dom4j.Node; @@ -35,9 +31,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; import eu.dnetlib.dhp.model.mdstore.Provenance; -import eu.dnetlib.message.Message; import eu.dnetlib.message.MessageManager; -import eu.dnetlib.message.MessageType; public class GenerateNativeStoreSparkJob { @@ -46,100 +40,62 @@ public class GenerateNativeStoreSparkJob { public static void main(String[] args) throws Exception { final ArgumentApplicationParser parser = new ArgumentApplicationParser( - IOUtils - .toString( - GenerateNativeStoreSparkJob.class - .getResourceAsStream( - "/eu/dnetlib/dhp/collection/collection_input_parameters.json"))); + IOUtils + .toString( + GenerateNativeStoreSparkJob.class + .getResourceAsStream( + "/eu/dnetlib/dhp/collection/collection_input_parameters.json"))); parser.parseArgument(args); final ObjectMapper jsonMapper = new ObjectMapper(); - final Provenance provenance = jsonMapper.readValue(parser.get("provenance"), Provenance.class); - final long dateOfCollection = new Long(parser.get("dateOfCollection")); + final String provenanceArgument = parser.get("provenance"); + log.info("Provenance is {}", provenanceArgument); + final Provenance provenance = jsonMapper.readValue(provenanceArgument, Provenance.class); + final String dateOfCollectionArgs = parser.get("dateOfCollection"); + log.info("dateOfCollection is {}", dateOfCollectionArgs); + final long dateOfCollection = new Long(dateOfCollectionArgs); + final String sequenceFileInputPath = parser.get("input"); + log.info("sequenceFileInputPath is {}", dateOfCollectionArgs); Boolean isSparkSessionManaged = Optional - .ofNullable(parser.get("isSparkSessionManaged")) - .map(Boolean::valueOf) - .orElse(Boolean.TRUE); + .ofNullable(parser.get("isSparkSessionManaged")) + .map(Boolean::valueOf) + .orElse(Boolean.TRUE); log.info("isSparkSessionManaged: {}", isSparkSessionManaged); - final Map ongoingMap = new HashMap<>(); - final Map reportMap = new HashMap<>(); - - final boolean test = parser.get("isTest") == null ? false : Boolean.valueOf(parser.get("isTest")); - SparkConf conf = new SparkConf(); runWithSparkSession( - conf, - isSparkSessionManaged, - spark -> { - final JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); + conf, + isSparkSessionManaged, + spark -> { + final JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); - final JavaPairRDD inputRDD = sc - .sequenceFile(parser.get("input"), IntWritable.class, Text.class); + final JavaPairRDD inputRDD = sc + .sequenceFile(sequenceFileInputPath, IntWritable.class, Text.class); - final LongAccumulator totalItems = sc.sc().longAccumulator("TotalItems"); - final LongAccumulator invalidRecords = sc.sc().longAccumulator("InvalidRecords"); + final LongAccumulator totalItems = sc.sc().longAccumulator("TotalItems"); + final LongAccumulator invalidRecords = sc.sc().longAccumulator("InvalidRecords"); - final MessageManager manager = new MessageManager( - parser.get("rabbitHost"), - parser.get("rabbitUser"), - parser.get("rabbitPassword"), - false, - false, - null); + final JavaRDD nativeStore = inputRDD + .map( + item -> parseRecord( + item._2().toString(), + parser.get("xpath"), + parser.get("encoding"), + provenance, + dateOfCollection, + totalItems, + invalidRecords)) + .filter(Objects::nonNull) + .distinct(); - final JavaRDD mappeRDD = inputRDD - .map( - item -> parseRecord( - item._2().toString(), - parser.get("xpath"), - parser.get("encoding"), - provenance, - dateOfCollection, - totalItems, - invalidRecords)) - .filter(Objects::nonNull) - .distinct(); + final Encoder encoder = Encoders.bean(MetadataRecord.class); + final Dataset mdstore = spark.createDataset(nativeStore.rdd(), encoder); + final LongAccumulator mdStoreRecords = sc.sc().longAccumulator("MDStoreRecords"); + mdStoreRecords.add(mdstore.count()); - ongoingMap.put("ongoing", "0"); - if (!test) { - manager - .sendMessage( - new Message( - parser.get("workflowId"), "DataFrameCreation", MessageType.ONGOING, ongoingMap), - parser.get("rabbitOngoingQueue"), - true, - false); - } + mdstore.write().format("parquet").save(parser.get("output")); - final Encoder encoder = Encoders.bean(MetadataRecord.class); - final Dataset mdstore = spark.createDataset(mappeRDD.rdd(), encoder); - final LongAccumulator mdStoreRecords = sc.sc().longAccumulator("MDStoreRecords"); - mdStoreRecords.add(mdstore.count()); - ongoingMap.put("ongoing", "" + totalItems.value()); - if (!test) { - manager - .sendMessage( - new Message( - parser.get("workflowId"), "DataFrameCreation", MessageType.ONGOING, ongoingMap), - parser.get("rabbitOngoingQueue"), - true, - false); - } - mdstore.write().format("parquet").save(parser.get("output")); - reportMap.put("inputItem", "" + totalItems.value()); - reportMap.put("invalidRecords", "" + invalidRecords.value()); - reportMap.put("mdStoreSize", "" + mdStoreRecords.value()); - if (!test) { - manager - .sendMessage( - new Message(parser.get("workflowId"), "Collection", MessageType.REPORT, reportMap), - parser.get("rabbitReportQueue"), - true, - false); - manager.close(); - } - }); + }); } @@ -166,12 +122,9 @@ public class GenerateNativeStoreSparkJob { } return new MetadataRecord(originalIdentifier, encoding, provenance, input, dateOfCollection); } catch (Throwable e) { - if (invalidRecords != null) - invalidRecords.add(1); - e.printStackTrace(); + invalidRecords.add(1); return null; } } - } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java index 4a0c70c45..7146e610e 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java @@ -4,9 +4,9 @@ package eu.dnetlib.dhp.collection.plugin; import java.util.stream.Stream; import eu.dnetlib.collector.worker.model.ApiDescriptor; -import eu.dnetlib.dhp.collection.worker.DnetCollectorException; +import eu.dnetlib.dhp.collection.worker.CollectorException; public interface CollectorPlugin { - Stream collect(ApiDescriptor api) throws DnetCollectorException; + Stream collect(ApiDescriptor api) throws CollectorException; } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java index 7f71f401d..c4c52271a 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java @@ -15,7 +15,7 @@ import com.google.common.collect.Lists; import eu.dnetlib.collector.worker.model.ApiDescriptor; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; -import eu.dnetlib.dhp.collection.worker.DnetCollectorException; +import eu.dnetlib.dhp.collection.worker.CollectorException; public class OaiCollectorPlugin implements CollectorPlugin { @@ -27,7 +27,7 @@ public class OaiCollectorPlugin implements CollectorPlugin { private OaiIteratorFactory oaiIteratorFactory; @Override - public Stream collect(final ApiDescriptor api) throws DnetCollectorException { + public Stream collect(final ApiDescriptor api) throws CollectorException { final String baseUrl = api.getBaseUrl(); final String mdFormat = api.getParams().get(FORMAT_PARAM); final String setParam = api.getParams().get(OAI_SET_PARAM); @@ -46,19 +46,19 @@ public class OaiCollectorPlugin implements CollectorPlugin { } if (baseUrl == null || baseUrl.isEmpty()) { - throw new DnetCollectorException("Param 'baseurl' is null or empty"); + throw new CollectorException("Param 'baseurl' is null or empty"); } if (mdFormat == null || mdFormat.isEmpty()) { - throw new DnetCollectorException("Param 'mdFormat' is null or empty"); + throw new CollectorException("Param 'mdFormat' is null or empty"); } if (fromDate != null && !fromDate.matches("\\d{4}-\\d{2}-\\d{2}")) { - throw new DnetCollectorException("Invalid date (YYYY-MM-DD): " + fromDate); + throw new CollectorException("Invalid date (YYYY-MM-DD): " + fromDate); } if (untilDate != null && !untilDate.matches("\\d{4}-\\d{2}-\\d{2}")) { - throw new DnetCollectorException("Invalid date (YYYY-MM-DD): " + untilDate); + throw new CollectorException("Invalid date (YYYY-MM-DD): " + untilDate); } final Iterator> iters = sets diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java index d61f13fb5..e54bae67d 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java @@ -16,7 +16,7 @@ import org.dom4j.DocumentException; import org.dom4j.Node; import org.dom4j.io.SAXReader; -import eu.dnetlib.dhp.collection.worker.DnetCollectorException; +import eu.dnetlib.dhp.collection.worker.CollectorException; import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; import eu.dnetlib.dhp.collection.worker.utils.XmlCleaner; @@ -58,7 +58,7 @@ public class OaiIterator implements Iterator { this.started = true; try { this.token = firstPage(); - } catch (final DnetCollectorException e) { + } catch (final CollectorException e) { throw new RuntimeException(e); } } @@ -80,7 +80,7 @@ public class OaiIterator implements Iterator { while (queue.isEmpty() && token != null && !token.isEmpty()) { try { token = otherPages(token); - } catch (final DnetCollectorException e) { + } catch (final CollectorException e) { throw new RuntimeException(e); } } @@ -92,7 +92,7 @@ public class OaiIterator implements Iterator { public void remove() { } - private String firstPage() throws DnetCollectorException { + private String firstPage() throws CollectorException { try { String url = baseUrl + "?verb=ListRecords&metadataPrefix=" + URLEncoder.encode(mdFormat, "UTF-8"); if (set != null && !set.isEmpty()) { @@ -108,7 +108,7 @@ public class OaiIterator implements Iterator { return downloadPage(url); } catch (final UnsupportedEncodingException e) { - throw new DnetCollectorException(e); + throw new CollectorException(e); } } @@ -126,18 +126,18 @@ public class OaiIterator implements Iterator { return result.trim(); } - private String otherPages(final String resumptionToken) throws DnetCollectorException { + private String otherPages(final String resumptionToken) throws CollectorException { try { return downloadPage( baseUrl + "?verb=ListRecords&resumptionToken=" + URLEncoder.encode(resumptionToken, "UTF-8")); } catch (final UnsupportedEncodingException e) { - throw new DnetCollectorException(e); + throw new CollectorException(e); } } - private String downloadPage(final String url) throws DnetCollectorException { + private String downloadPage(final String url) throws CollectorException { final String xml = httpConnector.getInputSource(url); Document doc; @@ -151,7 +151,7 @@ public class OaiIterator implements Iterator { } catch (final DocumentException e1) { final String resumptionToken = extractResumptionToken(xml); if (resumptionToken == null) { - throw new DnetCollectorException("Error parsing cleaned document:" + cleaned, e1); + throw new CollectorException("Error parsing cleaned document:" + cleaned, e1); } return resumptionToken; } @@ -164,7 +164,7 @@ public class OaiIterator implements Iterator { log.warn("noRecordsMatch for oai call: " + url); return null; } else { - throw new DnetCollectorException(code + " - " + errorNode.getText()); + throw new CollectorException(code + " - " + errorNode.getText()); } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/DnetCollectorException.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorException.java similarity index 56% rename from dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/DnetCollectorException.java rename to dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorException.java index f40962c21..71d225f13 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/DnetCollectorException.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorException.java @@ -1,16 +1,16 @@ package eu.dnetlib.dhp.collection.worker; -public class DnetCollectorException extends Exception { +public class CollectorException extends Exception { /** */ private static final long serialVersionUID = -290723075076039757L; - public DnetCollectorException() { + public CollectorException() { super(); } - public DnetCollectorException( + public CollectorException( final String message, final Throwable cause, final boolean enableSuppression, @@ -18,15 +18,15 @@ public class DnetCollectorException extends Exception { super(message, cause, enableSuppression, writableStackTrace); } - public DnetCollectorException(final String message, final Throwable cause) { + public CollectorException(final String message, final Throwable cause) { super(message, cause); } - public DnetCollectorException(final String message) { + public CollectorException(final String message) { super(message); } - public DnetCollectorException(final Throwable cause) { + public CollectorException(final Throwable cause) { super(cause); } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java new file mode 100644 index 000000000..380db641a --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java @@ -0,0 +1,93 @@ + +package eu.dnetlib.dhp.collection.worker; + +import java.io.IOException; +import java.net.URI; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.io.IntWritable; +import org.apache.hadoop.io.SequenceFile; +import org.apache.hadoop.io.Text; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.dnetlib.collector.worker.model.ApiDescriptor; +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; +import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginFactory; + +public class CollectorWorker { + + private static final Logger log = LoggerFactory.getLogger(CollectorWorker.class); + + private final CollectorPluginFactory collectorPluginFactory; + + private final ApiDescriptor api; + + private final String hdfsuri; + + private final String hdfsPath; + + public CollectorWorker( + final CollectorPluginFactory collectorPluginFactory, + final ApiDescriptor api, + final String hdfsuri, + final String hdfsPath) { + this.collectorPluginFactory = collectorPluginFactory; + this.api = api; + this.hdfsuri = hdfsuri; + this.hdfsPath = hdfsPath; + + } + + public void collect() throws CollectorException { + try { + final CollectorPlugin plugin = collectorPluginFactory.getPluginByProtocol(api.getProtocol()); + + // ====== Init HDFS File System Object + Configuration conf = new Configuration(); + // Set FileSystem URI + conf.set("fs.defaultFS", hdfsuri); + // Because of Maven + conf.set("fs.hdfs.impl", org.apache.hadoop.hdfs.DistributedFileSystem.class.getName()); + conf.set("fs.file.impl", org.apache.hadoop.fs.LocalFileSystem.class.getName()); + + System.setProperty("hadoop.home.dir", "/"); + // Get the filesystem - HDFS + FileSystem.get(URI.create(hdfsuri), conf); + Path hdfswritepath = new Path(hdfsPath); + + log.info("Created path " + hdfswritepath.toString()); + + final AtomicInteger counter = new AtomicInteger(0); + try (SequenceFile.Writer writer = SequenceFile + .createWriter( + conf, + SequenceFile.Writer.file(hdfswritepath), + SequenceFile.Writer.keyClass(IntWritable.class), + SequenceFile.Writer.valueClass(Text.class))) { + final IntWritable key = new IntWritable(counter.get()); + final Text value = new Text(); + plugin + .collect(api) + .forEach( + content -> { + key.set(counter.getAndIncrement()); + value.set(content); + try { + writer.append(key, value); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + } catch (Throwable e) { + throw new CollectorException("Error on collecting ", e); + } + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java new file mode 100644 index 000000000..5e8d0f9c2 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java @@ -0,0 +1,55 @@ + +package eu.dnetlib.dhp.collection.worker; + +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.dnetlib.collector.worker.model.ApiDescriptor; +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginFactory; + +/** + * DnetCollectortWorkerApplication is the main class responsible to start the Dnet Collection into HDFS. This module + * will be executed on the hadoop cluster and taking in input some parameters that tells it which is the right collector + * plugin to use and where store the data into HDFS path + * + * @author Sandro La Bruzzo + */ +public class CollectorWorkerApplication { + + private static final Logger log = LoggerFactory.getLogger(CollectorWorkerApplication.class); + + private static final CollectorPluginFactory collectorPluginFactory = new CollectorPluginFactory(); + + /** + * @param args + */ + public static void main(final String[] args) throws Exception { + + final ArgumentApplicationParser argumentParser = new ArgumentApplicationParser( + IOUtils + .toString( + CollectorWorker.class + .getResourceAsStream( + "/eu/dnetlib/dhp/collection/collector_parameter.json"))); + argumentParser.parseArgument(args); + + final String hdfsuri = argumentParser.get("namenode"); + + log.info("hdfsURI is {}", hdfsuri); + final String hdfsPath = argumentParser.get("hdfsPath"); + log.info("hdfsPath is {}" + hdfsPath); + final String apiDescriptor = argumentParser.get("apidescriptor"); + log.info("apiDescriptor is {}" + apiDescriptor); + + final ObjectMapper jsonMapper = new ObjectMapper(); + + final ApiDescriptor api = jsonMapper.readValue(apiDescriptor, ApiDescriptor.class); + + final CollectorWorker worker = new CollectorWorker(collectorPluginFactory, api, hdfsuri, hdfsPath); + worker.collect(); + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/DnetCollectorWorker.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/DnetCollectorWorker.java deleted file mode 100644 index e686ad518..000000000 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/DnetCollectorWorker.java +++ /dev/null @@ -1,139 +0,0 @@ - -package eu.dnetlib.dhp.collection.worker; - -import java.io.IOException; -import java.net.URI; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; - -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.FileSystem; -import org.apache.hadoop.fs.Path; -import org.apache.hadoop.io.IntWritable; -import org.apache.hadoop.io.SequenceFile; -import org.apache.hadoop.io.Text; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import eu.dnetlib.collector.worker.model.ApiDescriptor; -import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; -import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginFactory; -import eu.dnetlib.message.Message; -import eu.dnetlib.message.MessageManager; -import eu.dnetlib.message.MessageType; - -public class DnetCollectorWorker { - - private static final Logger log = LoggerFactory.getLogger(DnetCollectorWorker.class); - - private final CollectorPluginFactory collectorPluginFactory; - - private final ArgumentApplicationParser argumentParser; - - private final MessageManager manager; - - public DnetCollectorWorker( - final CollectorPluginFactory collectorPluginFactory, - final ArgumentApplicationParser argumentParser, - final MessageManager manager) - throws DnetCollectorException { - this.collectorPluginFactory = collectorPluginFactory; - this.argumentParser = argumentParser; - this.manager = manager; - } - - public void collect() throws DnetCollectorException { - try { - final ObjectMapper jsonMapper = new ObjectMapper(); - final ApiDescriptor api = jsonMapper.readValue(argumentParser.get("apidescriptor"), ApiDescriptor.class); - - final CollectorPlugin plugin = collectorPluginFactory.getPluginByProtocol(api.getProtocol()); - - final String hdfsuri = argumentParser.get("namenode"); - - // ====== Init HDFS File System Object - Configuration conf = new Configuration(); - // Set FileSystem URI - conf.set("fs.defaultFS", hdfsuri); - // Because of Maven - conf.set("fs.hdfs.impl", org.apache.hadoop.hdfs.DistributedFileSystem.class.getName()); - conf.set("fs.file.impl", org.apache.hadoop.fs.LocalFileSystem.class.getName()); - - System.setProperty("HADOOP_USER_NAME", argumentParser.get("userHDFS")); - System.setProperty("hadoop.home.dir", "/"); - // Get the filesystem - HDFS - FileSystem.get(URI.create(hdfsuri), conf); - Path hdfswritepath = new Path(argumentParser.get("hdfsPath")); - - log.info("Created path " + hdfswritepath.toString()); - - final Map ongoingMap = new HashMap<>(); - final Map reportMap = new HashMap<>(); - final AtomicInteger counter = new AtomicInteger(0); - try (SequenceFile.Writer writer = SequenceFile - .createWriter( - conf, - SequenceFile.Writer.file(hdfswritepath), - SequenceFile.Writer.keyClass(IntWritable.class), - SequenceFile.Writer.valueClass(Text.class))) { - final IntWritable key = new IntWritable(counter.get()); - final Text value = new Text(); - plugin - .collect(api) - .forEach( - content -> { - key.set(counter.getAndIncrement()); - value.set(content); - if (counter.get() % 10 == 0) { - try { - ongoingMap.put("ongoing", "" + counter.get()); - log - .debug( - "Sending message: " - + manager - .sendMessage( - new Message( - argumentParser.get("workflowId"), - "Collection", - MessageType.ONGOING, - ongoingMap), - argumentParser.get("rabbitOngoingQueue"), - true, - false)); - } catch (Exception e) { - log.error("Error on sending message ", e); - } - } - try { - writer.append(key, value); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - } - ongoingMap.put("ongoing", "" + counter.get()); - manager - .sendMessage( - new Message( - argumentParser.get("workflowId"), "Collection", MessageType.ONGOING, ongoingMap), - argumentParser.get("rabbitOngoingQueue"), - true, - false); - reportMap.put("collected", "" + counter.get()); - manager - .sendMessage( - new Message( - argumentParser.get("workflowId"), "Collection", MessageType.REPORT, reportMap), - argumentParser.get("rabbitOngoingQueue"), - true, - false); - manager.close(); - } catch (Throwable e) { - throw new DnetCollectorException("Error on collecting ", e); - } - } -} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/DnetCollectorWorkerApplication.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/DnetCollectorWorkerApplication.java deleted file mode 100644 index da30e8793..000000000 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/DnetCollectorWorkerApplication.java +++ /dev/null @@ -1,49 +0,0 @@ - -package eu.dnetlib.dhp.collection.worker; - -import org.apache.commons.io.IOUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginFactory; -import eu.dnetlib.message.MessageManager; - -/** - * DnetCollectortWorkerApplication is the main class responsible to start the Dnet Collection into HDFS. This module - * will be executed on the hadoop cluster and taking in input some parameters that tells it which is the right collector - * plugin to use and where store the data into HDFS path - * - * @author Sandro La Bruzzo - */ -public class DnetCollectorWorkerApplication { - - private static final Logger log = LoggerFactory.getLogger(DnetCollectorWorkerApplication.class); - - private static final CollectorPluginFactory collectorPluginFactory = new CollectorPluginFactory(); - - private static ArgumentApplicationParser argumentParser; - - /** @param args */ - public static void main(final String[] args) throws Exception { - - argumentParser = new ArgumentApplicationParser( - IOUtils - .toString( - DnetCollectorWorker.class - .getResourceAsStream( - "/eu/dnetlib/collector/worker/collector_parameter.json"))); - argumentParser.parseArgument(args); - log.info("hdfsPath =" + argumentParser.get("hdfsPath")); - log.info("json = " + argumentParser.get("apidescriptor")); - final MessageManager manager = new MessageManager( - argumentParser.get("rabbitHost"), - argumentParser.get("rabbitUser"), - argumentParser.get("rabbitPassword"), - false, - false, - null); - final DnetCollectorWorker worker = new DnetCollectorWorker(collectorPluginFactory, argumentParser, manager); - worker.collect(); - } -} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginFactory.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginFactory.java index 7a0028e79..6b070b191 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginFactory.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginFactory.java @@ -3,18 +3,18 @@ package eu.dnetlib.dhp.collection.worker.utils; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; import eu.dnetlib.dhp.collection.plugin.oai.OaiCollectorPlugin; -import eu.dnetlib.dhp.collection.worker.DnetCollectorException; +import eu.dnetlib.dhp.collection.worker.CollectorException; public class CollectorPluginFactory { - public CollectorPlugin getPluginByProtocol(final String protocol) throws DnetCollectorException { + public CollectorPlugin getPluginByProtocol(final String protocol) throws CollectorException { if (protocol == null) - throw new DnetCollectorException("protocol cannot be null"); + throw new CollectorException("protocol cannot be null"); switch (protocol.toLowerCase().trim()) { case "oai": return new OaiCollectorPlugin(); default: - throw new DnetCollectorException("UNknown protocol"); + throw new CollectorException("UNknown protocol"); } } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector.java index 5d6108fad..ff3c18aba 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector.java @@ -19,7 +19,7 @@ import org.apache.commons.lang.math.NumberUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import eu.dnetlib.dhp.collection.worker.DnetCollectorException; +import eu.dnetlib.dhp.collection.worker.CollectorException; public class HttpConnector { @@ -42,9 +42,9 @@ public class HttpConnector { * * @param requestUrl the URL * @return the content of the downloaded resource - * @throws DnetCollectorException when retrying more than maxNumberOfRetry times + * @throws CollectorException when retrying more than maxNumberOfRetry times */ - public String getInputSource(final String requestUrl) throws DnetCollectorException { + public String getInputSource(final String requestUrl) throws CollectorException { return attemptDownlaodAsString(requestUrl, 1, new CollectorPluginErrorLogList()); } @@ -53,15 +53,15 @@ public class HttpConnector { * * @param requestUrl the URL * @return the content of the downloaded resource as InputStream - * @throws DnetCollectorException when retrying more than maxNumberOfRetry times + * @throws CollectorException when retrying more than maxNumberOfRetry times */ - public InputStream getInputSourceAsStream(final String requestUrl) throws DnetCollectorException { + public InputStream getInputSourceAsStream(final String requestUrl) throws CollectorException { return attemptDownload(requestUrl, 1, new CollectorPluginErrorLogList()); } private String attemptDownlaodAsString( final String requestUrl, final int retryNumber, final CollectorPluginErrorLogList errorList) - throws DnetCollectorException { + throws CollectorException { try { final InputStream s = attemptDownload(requestUrl, 1, new CollectorPluginErrorLogList()); try { @@ -75,16 +75,16 @@ public class HttpConnector { IOUtils.closeQuietly(s); } } catch (final InterruptedException e) { - throw new DnetCollectorException(e); + throw new CollectorException(e); } } private InputStream attemptDownload( final String requestUrl, final int retryNumber, final CollectorPluginErrorLogList errorList) - throws DnetCollectorException { + throws CollectorException { if (retryNumber > maxNumberOfRetry) { - throw new DnetCollectorException("Max number of retries exceeded. Cause: \n " + errorList); + throw new CollectorException("Max number of retries exceeded. Cause: \n " + errorList); } log.debug("Downloading " + requestUrl + " - try: " + retryNumber); @@ -144,7 +144,7 @@ public class HttpConnector { return attemptDownload(requestUrl, retryNumber + 1, errorList); } } catch (final InterruptedException e) { - throw new DnetCollectorException(e); + throw new CollectorException(e); } } @@ -173,13 +173,13 @@ public class HttpConnector { } private String obtainNewLocation(final Map> headerMap) - throws DnetCollectorException { + throws CollectorException { for (final String key : headerMap.keySet()) { if (key != null && key.toLowerCase().equals("location") && headerMap.get(key).size() > 0) { return headerMap.get(key).get(0); } } - throw new DnetCollectorException( + throw new CollectorException( "The requested url has been MOVED, but 'location' param is MISSING"); } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/DnetTransformationException.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/DnetTransformationException.java index 2c932e40b..45bd844e2 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/DnetTransformationException.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/DnetTransformationException.java @@ -1,28 +1,29 @@ + package eu.dnetlib.dhp.transformation; public class DnetTransformationException extends Exception { - public DnetTransformationException() { - super(); - } + public DnetTransformationException() { + super(); + } - public DnetTransformationException( - final String message, - final Throwable cause, - final boolean enableSuppression, - final boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } + public DnetTransformationException( + final String message, + final Throwable cause, + final boolean enableSuppression, + final boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } - public DnetTransformationException(final String message, final Throwable cause) { - super(message, cause); - } + public DnetTransformationException(final String message, final Throwable cause) { + super(message, cause); + } - public DnetTransformationException(final String message) { - super(message); - } + public DnetTransformationException(final String message) { + super(message); + } - public DnetTransformationException(final Throwable cause) { - super(cause); - } + public DnetTransformationException(final Throwable cause) { + super(cause); + } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java index 6e07e5173..c6ed5a1e3 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java @@ -9,11 +9,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import eu.dnetlib.dhp.aggregation.common.AggregationCounter; -import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; -import eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction; -import eu.dnetlib.dhp.utils.ISLookupClientFactory; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.spark.SparkConf; @@ -30,10 +25,15 @@ import org.dom4j.io.SAXReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import eu.dnetlib.dhp.aggregation.common.AggregationCounter; import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; import eu.dnetlib.dhp.transformation.vocabulary.VocabularyHelper; +import eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction; import eu.dnetlib.dhp.utils.DHPUtils; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; import eu.dnetlib.message.Message; import eu.dnetlib.message.MessageManager; import eu.dnetlib.message.MessageType; @@ -59,10 +59,9 @@ public class TransformSparkJobNode { .orElse(Boolean.TRUE); log.info("isSparkSessionManaged: {}", isSparkSessionManaged); - final String inputPath = parser.get("input"); - final String outputPath = parser.get("output"); + final String inputPath = parser.get("mdstoreInputPath"); + final String outputPath = parser.get("mdstoreOutputPath"); // TODO this variable will be used after implementing Messaging with DNet Aggregator - final String workflowId = parser.get("workflowId"); final String isLookupUrl = parser.get("isLookupUrl"); log.info(String.format("isLookupUrl: %s", isLookupUrl)); @@ -76,24 +75,22 @@ public class TransformSparkJobNode { spark -> transformRecords(parser.getObjectMap(), isLookupService, spark, inputPath, outputPath)); } - - public static void transformRecords(final Mapargs, final ISLookUpService isLookUpService, final SparkSession spark, final String inputPath, final String outputPath) throws DnetTransformationException { + public static void transformRecords(final Map args, final ISLookUpService isLookUpService, + final SparkSession spark, final String inputPath, final String outputPath) throws DnetTransformationException { final LongAccumulator totalItems = spark.sparkContext().longAccumulator("TotalItems"); final LongAccumulator errorItems = spark.sparkContext().longAccumulator("errorItems"); final LongAccumulator transformedItems = spark.sparkContext().longAccumulator("transformedItems"); - final AggregationCounter ct = new AggregationCounter(totalItems, errorItems,transformedItems ); + final AggregationCounter ct = new AggregationCounter(totalItems, errorItems, transformedItems); final Encoder encoder = Encoders.bean(MetadataRecord.class); final Dataset mdstoreInput = spark.read().format("parquet").load(inputPath).as(encoder); - final MapFunction XSLTTransformationFunction = TransformationFactory.getTransformationPlugin(args,ct, isLookUpService); + final MapFunction XSLTTransformationFunction = TransformationFactory + .getTransformationPlugin(args, ct, isLookUpService); mdstoreInput.map(XSLTTransformationFunction, encoder).write().save(outputPath); - log.info("Transformed item "+ ct.getProcessedItems().count()); - log.info("Total item "+ ct.getTotalItems().count()); - log.info("Transformation Error item "+ ct.getErrorItems().count()); + log.info("Transformed item " + ct.getProcessedItems().count()); + log.info("Total item " + ct.getTotalItems().count()); + log.info("Transformation Error item " + ct.getErrorItems().count()); } - - - } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java index 0296458a5..58292139a 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java @@ -1,62 +1,69 @@ + package eu.dnetlib.dhp.transformation; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.apache.spark.api.java.function.MapFunction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import eu.dnetlib.dhp.aggregation.common.AggregationCounter; import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; import eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import org.apache.commons.lang3.StringUtils; -import org.apache.spark.api.java.function.MapFunction; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; -import java.util.Map; public class TransformationFactory { - private static final Logger log = LoggerFactory.getLogger(TransformationFactory.class); - public static final String TRULE_XQUERY = "for $x in collection('/db/DRIVER/TransformationRuleDSResources/TransformationRuleDSResourceType') where $x//TITLE = \"%s\" return $x//CODE/text()"; + private static final Logger log = LoggerFactory.getLogger(TransformationFactory.class); + public static final String TRULE_XQUERY = "for $x in collection('/db/DRIVER/TransformationRuleDSResources/TransformationRuleDSResourceType') where $x//TITLE = \"%s\" return $x//CODE/text()"; + public static MapFunction getTransformationPlugin( + final Map jobArgument, final AggregationCounter counters, final ISLookUpService isLookupService) + throws DnetTransformationException { - public static MapFunction getTransformationPlugin(final Map jobArgument, final AggregationCounter counters, final ISLookUpService isLookupService) throws DnetTransformationException { + try { + final String transformationPlugin = jobArgument.get("transformationPlugin"); - try { - final String transformationPlugin = jobArgument.get("transformationPlugin"); + log.info("Transformation plugin required " + transformationPlugin); + switch (transformationPlugin) { + case "XSLT_TRANSFORM": { + final String transformationRuleName = jobArgument.get("transformationRuleTitle"); + if (StringUtils.isBlank(transformationRuleName)) + throw new DnetTransformationException("Missing Parameter transformationRule"); + final VocabularyGroup vocabularies = VocabularyGroup.loadVocsFromIS(isLookupService); - log.info("Transformation plugin required "+transformationPlugin); - switch (transformationPlugin) { - case "XSLT_TRANSFORM": { - final String transformationRuleName = jobArgument.get("transformationRule"); - if (StringUtils.isBlank(transformationRuleName)) - throw new DnetTransformationException("Missing Parameter transformationRule"); - final VocabularyGroup vocabularies = VocabularyGroup.loadVocsFromIS(isLookupService); + final String transformationRule = queryTransformationRuleFromIS( + transformationRuleName, isLookupService); - final String transformationRule = queryTransformationRuleFromIS(transformationRuleName, isLookupService); + final long dateOfTransformation = new Long(jobArgument.get("dateOfTransformation")); + return new XSLTTransformationFunction(counters, transformationRule, dateOfTransformation, + vocabularies); - final long dateOfTransformation = new Long(jobArgument.get("dateOfTransformation")); - return new XSLTTransformationFunction(counters,transformationRule,dateOfTransformation,vocabularies); + } + default: + throw new DnetTransformationException( + "transformation plugin does not exists for " + transformationPlugin); - } - default: - throw new DnetTransformationException("transformation plugin does not exists for " + transformationPlugin); + } - } + } catch (Throwable e) { + throw new DnetTransformationException(e); + } + } - } catch (Throwable e) { - throw new DnetTransformationException(e); - } - } - - private static String queryTransformationRuleFromIS(final String transformationRuleName, final ISLookUpService isLookUpService) throws Exception { - final String query = String.format(TRULE_XQUERY, transformationRuleName); - log.info("asking query to IS: "+ query); - List result = isLookUpService.quickSearchProfile(query); - - if (result==null || result.isEmpty()) - throw new DnetTransformationException("Unable to find transformation rule with name: "+ transformationRuleName); - return result.get(0); - } + private static String queryTransformationRuleFromIS(final String transformationRuleName, + final ISLookUpService isLookUpService) throws Exception { + final String query = String.format(TRULE_XQUERY, transformationRuleName); + log.info("asking query to IS: " + query); + List result = isLookUpService.quickSearchProfile(query); + if (result == null || result.isEmpty()) + throw new DnetTransformationException( + "Unable to find transformation rule with name: " + transformationRuleName); + return result.get(0); + } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java index 2c6d776af..7b0fdd484 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java @@ -1,7 +1,6 @@ package eu.dnetlib.dhp.transformation.xslt; - import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.schema.oaf.Qualifier; import net.sf.saxon.s9api.*; @@ -40,6 +39,6 @@ public class Cleaner implements ExtensionFunction, Serializable { Qualifier cleanedValue = vocabularies.getSynonymAsQualifier(vocabularyName, currentValue); return new XdmAtomicValue( - cleanedValue != null ? cleanedValue.getClassid() : currentValue); + cleanedValue != null ? cleanedValue.getClassid() : currentValue); } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java index c02b83345..d8707cd76 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java @@ -1,66 +1,68 @@ package eu.dnetlib.dhp.transformation.xslt; +import java.io.ByteArrayInputStream; +import java.io.StringWriter; + +import javax.xml.transform.stream.StreamSource; + +import org.apache.spark.api.java.function.MapFunction; + import eu.dnetlib.dhp.aggregation.common.AggregationCounter; import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; import net.sf.saxon.s9api.*; -import org.apache.spark.api.java.function.MapFunction; - -import javax.xml.transform.stream.StreamSource; -import java.io.ByteArrayInputStream; -import java.io.StringWriter; public class XSLTTransformationFunction implements MapFunction { - private final AggregationCounter aggregationCounter; + private final AggregationCounter aggregationCounter; - private final String transformationRule; + private final String transformationRule; - private final Cleaner cleanFunction; + private final Cleaner cleanFunction; - private final long dateOfTransformation; + private final long dateOfTransformation; - public XSLTTransformationFunction( - final AggregationCounter aggregationCounter, - final String transformationRule, - long dateOfTransformation, - final VocabularyGroup vocabularies) - throws Exception { - this.aggregationCounter = aggregationCounter; - this.transformationRule = transformationRule; - this.dateOfTransformation = dateOfTransformation; - cleanFunction = new Cleaner(vocabularies); - } + public XSLTTransformationFunction( + final AggregationCounter aggregationCounter, + final String transformationRule, + long dateOfTransformation, + final VocabularyGroup vocabularies) + throws Exception { + this.aggregationCounter = aggregationCounter; + this.transformationRule = transformationRule; + this.dateOfTransformation = dateOfTransformation; + cleanFunction = new Cleaner(vocabularies); + } - @Override - public MetadataRecord call(MetadataRecord value) { - aggregationCounter.getTotalItems().add(1); - try { - Processor processor = new Processor(false); - processor.registerExtensionFunction(cleanFunction); - final XsltCompiler comp = processor.newXsltCompiler(); - XsltExecutable xslt = comp - .compile(new StreamSource(new ByteArrayInputStream(transformationRule.getBytes()))); - XdmNode source = processor - .newDocumentBuilder() - .build(new StreamSource(new ByteArrayInputStream(value.getBody().getBytes()))); - XsltTransformer trans = xslt.load(); - trans.setInitialContextNode(source); - final StringWriter output = new StringWriter(); - Serializer out = processor.newSerializer(output); - out.setOutputProperty(Serializer.Property.METHOD, "xml"); - out.setOutputProperty(Serializer.Property.INDENT, "yes"); - trans.setDestination(out); - trans.transform(); - final String xml = output.toString(); - value.setBody(xml); - value.setDateOfTransformation(dateOfTransformation); - aggregationCounter.getProcessedItems().add(1); - return value; - } catch (Throwable e) { - aggregationCounter.getErrorItems().add(1); - return null; - } - } + @Override + public MetadataRecord call(MetadataRecord value) { + aggregationCounter.getTotalItems().add(1); + try { + Processor processor = new Processor(false); + processor.registerExtensionFunction(cleanFunction); + final XsltCompiler comp = processor.newXsltCompiler(); + XsltExecutable xslt = comp + .compile(new StreamSource(new ByteArrayInputStream(transformationRule.getBytes()))); + XdmNode source = processor + .newDocumentBuilder() + .build(new StreamSource(new ByteArrayInputStream(value.getBody().getBytes()))); + XsltTransformer trans = xslt.load(); + trans.setInitialContextNode(source); + final StringWriter output = new StringWriter(); + Serializer out = processor.newSerializer(output); + out.setOutputProperty(Serializer.Property.METHOD, "xml"); + out.setOutputProperty(Serializer.Property.INDENT, "yes"); + trans.setDestination(out); + trans.transform(); + final String xml = output.toString(); + value.setBody(xml); + value.setDateOfTransformation(dateOfTransformation); + aggregationCounter.getProcessedItems().add(1); + return value; + } catch (Throwable e) { + aggregationCounter.getErrorItems().add(1); + return null; + } + } } diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collection_input_parameters.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collection_input_parameters.json index 4a6aec5ee..7f5113930 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collection_input_parameters.json +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collection_input_parameters.json @@ -41,46 +41,10 @@ "paramDescription": "the path of the result DataFrame on HDFS", "paramRequired": true }, - { - "paramName": "ru", - "paramLongName": "rabbitUser", - "paramDescription": "the user to connect with RabbitMq for messaging", - "paramRequired": true - }, - { - "paramName": "rp", - "paramLongName": "rabbitPassword", - "paramDescription": "the password to connect with RabbitMq for messaging", - "paramRequired": true - }, - { - "paramName": "rh", - "paramLongName": "rabbitHost", - "paramDescription": "the host of the RabbitMq server", - "paramRequired": true - }, - { - "paramName": "ro", - "paramLongName": "rabbitOngoingQueue", - "paramDescription": "the name of the ongoing queue", - "paramRequired": true - }, - { - "paramName": "rr", - "paramLongName": "rabbitReportQueue", - "paramDescription": "the name of the report queue", - "paramRequired": true - }, { "paramName": "w", "paramLongName": "workflowId", "paramDescription": "the identifier of the dnet Workflow", - "paramRequired": true - }, - { - "paramName": "t", - "paramLongName": "isTest", - "paramDescription": "the name of the report queue", "paramRequired": false } ] \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_parameter.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_parameter.json new file mode 100644 index 000000000..901664e0d --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_parameter.json @@ -0,0 +1,6 @@ +[ + {"paramName":"p", "paramLongName":"hdfsPath", "paramDescription": "the path where storing the sequential file", "paramRequired": true}, + {"paramName":"a", "paramLongName":"apidescriptor", "paramDescription": "the JSON encoding of the API Descriptor", "paramRequired": true}, + {"paramName":"n", "paramLongName":"namenode", "paramDescription": "the Name Node URI", "paramRequired": true}, + {"paramName":"w", "paramLongName":"workflowId", "paramDescription": "the identifier of the dnet Workflow", "paramRequired": false} +] \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/config-default.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/config-default.xml new file mode 100644 index 000000000..2e0ed9aee --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/config-default.xml @@ -0,0 +1,18 @@ + + + jobTracker + yarnRM + + + nameNode + hdfs://nameservice1 + + + oozie.use.system.libpath + true + + + oozie.action.sharelib.for.spark + spark2 + + \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml index 3e7f68401..38cd83da7 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml @@ -1,10 +1,5 @@ - - sequenceFilePath - the path to store the sequence file of the native metadata collected - - mdStorePath the path of the native mdstore @@ -39,72 +34,52 @@ The identifier of the workflow + + ${jobTracker} + ${nameNode} + - + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] - - - - - - - - - - + - ${jobTracker} - ${nameNode} - eu.dnetlib.dhp.collection.worker.DnetCollectorWorker - -p${sequenceFilePath} - -a${apiDescription} - -n${nameNode} - -rh${rmq_host} - -ru${rmq_user} - -rp${rmq_pwd} - -rr${rmq_report} - -ro${rmq_ongoing} - -usandro.labruzzo - -w${workflowId} + eu.dnetlib.dhp.collection.worker.CollectorWorkerApplication + --hdfsPath${workingDir}/sequenceFile_${mdstoreVersion} + --apidescriptor${apiDescription} + --namenode${nameNode} + - ${jobTracker} - ${nameNode} yarn cluster - GenerateNativeStoreSparkJob + Generate Native MetadataStore eu.dnetlib.dhp.collection.GenerateNativeStoreSparkJob - dhp-aggregations-1.0.0-SNAPSHOT.jar - --num-executors 50 --conf spark.yarn.jars="hdfs://hadoop-rm1.garr-pa1.d4science.org:8020/user/oozie/share/lib/lib_20180405103059/spark2" - --encoding ${metadataEncoding} - --dateOfCollection ${timestamp} - --provenance ${dataSourceInfo} + dhp-aggregation-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + + --encoding${metadataEncoding} + --dateOfCollection${timestamp} + --provenance${dataSourceInfo} --xpath${identifierPath} - --input${sequenceFilePath} + --input${workingDir}/sequenceFile --output${mdStorePath} - -rh${rmq_host} - -ru${rmq_user} - -rp${rmq_pwd} - -rr${rmq_report} - -ro${rmq_ongoing} -w${workflowId} - - - - - - - - diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collector/worker/collector_parameter.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collector/worker/collector_parameter.json deleted file mode 100644 index c247d15e4..000000000 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collector/worker/collector_parameter.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - {"paramName":"p", "paramLongName":"hdfsPath", "paramDescription": "the path where storing the sequential file", "paramRequired": true}, - {"paramName":"a", "paramLongName":"apidescriptor", "paramDescription": "the JSON encoding of the API Descriptor", "paramRequired": true}, - {"paramName":"n", "paramLongName":"namenode", "paramDescription": "the Name Node URI", "paramRequired": true}, - {"paramName":"u", "paramLongName":"userHDFS", "paramDescription": "the user wich create the hdfs seq file", "paramRequired": true}, - {"paramName":"ru", "paramLongName":"rabbitUser", "paramDescription": "the user to connect with RabbitMq for messaging", "paramRequired": true}, - {"paramName":"rp", "paramLongName":"rabbitPassword", "paramDescription": "the password to connect with RabbitMq for messaging", "paramRequired": true}, - {"paramName":"rh", "paramLongName":"rabbitHost", "paramDescription": "the host of the RabbitMq server", "paramRequired": true}, - {"paramName":"ro", "paramLongName":"rabbitOngoingQueue", "paramDescription": "the name of the ongoing queue", "paramRequired": true}, - {"paramName":"rr", "paramLongName":"rabbitReportQueue", "paramDescription": "the name of the report queue", "paramRequired": true}, - {"paramName":"w", "paramLongName":"workflowId", "paramDescription": "the identifier of the dnet Workflow", "paramRequired": true} -] \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/config-default.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/config-default.xml new file mode 100644 index 000000000..2e0ed9aee --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/config-default.xml @@ -0,0 +1,18 @@ + + + jobTracker + yarnRM + + + nameNode + hdfs://nameservice1 + + + oozie.use.system.libpath + true + + + oozie.action.sharelib.for.spark + spark2 + + \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml index 4b1e3d84b..b36bc3766 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml @@ -2,7 +2,7 @@ mdstoreInputPath - the path of the input MDStore + the path of the native MDStore @@ -11,66 +11,57 @@ - transformationRule + transformationRuleTitle The transformation Rule to apply - timestamp - The timestamp of the collection date + transformationPlugin + The transformation Plugin - workflowId - The identifier of the workflow + dateOfTransformation + The timestamp of the transformation date + + - + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] - - - - - - - - + - ${jobTracker} - ${nameNode} yarn cluster - MDBuilder + Transform MetadataStore eu.dnetlib.dhp.transformation.TransformSparkJobNode - dhp-aggregations-1.0.0-SNAPSHOT.jar - --num-executors 50 --conf spark.yarn.jars="hdfs://hadoop-rm1.garr-pa1.d4science.org:8020/user/oozie/share/lib/lib_20180405103059/spark2" - --dateOfCollection ${timestamp} - -mt yarn - --input${mdstoreInputPath} - --output${mdstoreOutputPath} - -w${workflowId} - -tr${transformationRule} - -ru${rmq_user} - -rp${rmq_pwd} - -rh${rmq_host} - -ro${rmq_ongoing} - -rr${rmq_report} + dhp-aggregations-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + + --mdstoreInputPath${mdstoreInputPath} + --mdstoreOutputPath${mdstoreOutputPath} + --dateOfTransformation${dateOfTransformation} + --transformationPlugin${transformationPlugin} + --transformationRuleTitle${transformationRuleTitle} + + - - - - - - - - + + \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/transformation_input_parameters.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/transformation_input_parameters.json index fd2a96ea0..cbd2f25ab 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/transformation_input_parameters.json +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/transformation_input_parameters.json @@ -13,28 +13,32 @@ }, { "paramName": "i", - "paramLongName": "input", + "paramLongName": "mdstoreInputPath", "paramDescription": "the path of the sequencial file to read", "paramRequired": true }, { "paramName": "o", - "paramLongName": "output", + "paramLongName": "mdstoreOutputPath", "paramDescription": "the path of the result DataFrame on HDFS", "paramRequired": true }, - { - "paramName": "w", - "paramLongName": "workflowId", - "paramDescription": "the identifier of the dnet Workflow", - "paramRequired": true - }, { "paramName": "tr", - "paramLongName": "transformationRule", + "paramLongName": "transformationRuleTitle", "paramDescription": "the transformation Rule to apply to the input MDStore", "paramRequired": true }, + + { + "paramName": "i", + "paramLongName": "isLookupUrl", + "paramDescription": "the Information System Service LookUp URL", + "paramRequired": true + }, + + + { "paramName": "tp", "paramLongName": "transformationPlugin", diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java index c1142ad9c..5c37e9ec3 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java @@ -6,16 +6,15 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; -import eu.dnetlib.dhp.collection.worker.DnetCollectorException; -import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; - import eu.dnetlib.dhp.actionmanager.project.utils.EXCELParser; +import eu.dnetlib.dhp.collection.worker.CollectorException; +import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; @Disabled public class EXCELParserTest { @@ -31,7 +30,7 @@ public class EXCELParserTest { } @Test - public void test1() throws DnetCollectorException, IOException, InvalidFormatException, ClassNotFoundException, + public void test1() throws CollectorException, IOException, InvalidFormatException, ClassNotFoundException, IllegalAccessException, InstantiationException { EXCELParser excelParser = new EXCELParser(); diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java index 3b9d1c3ab..f5ef280a0 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java @@ -1,8 +1,6 @@ package eu.dnetlib.dhp.actionmanager.project.httpconnector; -import eu.dnetlib.dhp.collection.worker.DnetCollectorException; -import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; @@ -11,6 +9,9 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import eu.dnetlib.dhp.collection.worker.CollectorException; +import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; + @Disabled public class HttpConnectorTest { @@ -31,12 +32,12 @@ public class HttpConnectorTest { @Test - public void testGetInputSource() throws DnetCollectorException { + public void testGetInputSource() throws CollectorException { System.out.println(connector.getInputSource(URL)); } @Test - public void testGoodServers() throws DnetCollectorException { + public void testGoodServers() throws CollectorException { System.out.println(connector.getInputSource(URL_GOODSNI_SERVER)); } diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/DnetCollectorWorkerApplicationTests.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/DnetCollectorWorkerApplicationTests.java index c745219fe..fc19f2064 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/DnetCollectorWorkerApplicationTests.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/DnetCollectorWorkerApplicationTests.java @@ -5,17 +5,19 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.*; import java.io.File; +import java.nio.file.Path; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.collector.worker.model.ApiDescriptor; import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.collection.worker.DnetCollectorWorker; +import eu.dnetlib.dhp.collection.worker.CollectorWorker; import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginFactory; import eu.dnetlib.message.Message; import eu.dnetlib.message.MessageManager; @@ -23,43 +25,6 @@ import eu.dnetlib.message.MessageManager; @Disabled public class DnetCollectorWorkerApplicationTests { - private final ArgumentApplicationParser argumentParser = mock(ArgumentApplicationParser.class); - private final MessageManager messageManager = mock(MessageManager.class); - - private DnetCollectorWorker worker; - - @BeforeEach - public void setup() throws Exception { - ObjectMapper mapper = new ObjectMapper(); - final String apiJson = mapper.writeValueAsString(getApi()); - when(argumentParser.get("apidescriptor")).thenReturn(apiJson); - when(argumentParser.get("namenode")).thenReturn("file://tmp/test.seq"); - when(argumentParser.get("hdfsPath")).thenReturn("/tmp/file.seq"); - when(argumentParser.get("userHDFS")).thenReturn("sandro"); - when(argumentParser.get("workflowId")).thenReturn("sandro"); - when(argumentParser.get("rabbitOngoingQueue")).thenReturn("sandro"); - - when(messageManager.sendMessage(any(Message.class), anyString(), anyBoolean(), anyBoolean())) - .thenAnswer( - a -> { - System.out.println("sent message: " + a.getArguments()[0]); - return true; - }); - when(messageManager.sendMessage(any(Message.class), anyString())) - .thenAnswer( - a -> { - System.out.println("Called"); - return true; - }); - worker = new DnetCollectorWorker(new CollectorPluginFactory(), argumentParser, messageManager); - } - - @AfterEach - public void dropDown() { - File f = new File("/tmp/file.seq"); - f.delete(); - } - @Test public void testFindPlugin() throws Exception { final CollectorPluginFactory collectorPluginEnumerator = new CollectorPluginFactory(); @@ -79,8 +44,14 @@ public class DnetCollectorWorkerApplicationTests { } @Test - public void testFeeding() throws Exception { + public void testFeeding(@TempDir Path testDir) throws Exception { + + System.out.println(testDir.toString()); + CollectorWorker worker = new CollectorWorker(new CollectorPluginFactory(), getApi(), + "file://" + testDir.toString() + "/file.seq", testDir.toString() + "/file.seq"); worker.collect(); + + // TODO create ASSERT HERE } private ApiDescriptor getApi() { diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java index 5479e0b57..6a80e01e2 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java @@ -1,6 +1,7 @@ package eu.dnetlib.dhp.transformation; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.lenient; @@ -14,13 +15,13 @@ import java.util.stream.Stream; import javax.xml.transform.stream.StreamSource; -import eu.dnetlib.dhp.aggregation.common.AggregationCounter; -import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; -import eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.spark.SparkConf; +import org.apache.spark.api.java.function.FilterFunction; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoder; +import org.apache.spark.sql.Encoders; import org.apache.spark.sql.SparkSession; import org.apache.spark.util.LongAccumulator; import org.dom4j.Document; @@ -31,8 +32,14 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; + +import eu.dnetlib.dhp.aggregation.common.AggregationCounter; import eu.dnetlib.dhp.collection.CollectionJobTest; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; +import eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; @ExtendWith(MockitoExtension.class) public class TransformationJobTest { @@ -49,8 +56,8 @@ public class TransformationJobTest { lenient().when(isLookUpService.quickSearchProfile(VocabularyGroup.VOCABULARIES_XQUERY)).thenReturn(vocs()); lenient() - .when(isLookUpService.quickSearchProfile(VocabularyGroup.VOCABULARY_SYNONYMS_XQUERY)) - .thenReturn(synonyms()); + .when(isLookUpService.quickSearchProfile(VocabularyGroup.VOCABULARY_SYNONYMS_XQUERY)) + .thenReturn(synonyms()); vocabularies = VocabularyGroup.loadVocsFromIS(isLookUpService); } @@ -67,7 +74,6 @@ public class TransformationJobTest { spark.stop(); } - @Test @DisplayName("Test Transform Single XML using XSLTTransformator") public void testTransformSaxonHE() throws Exception { @@ -76,19 +82,15 @@ public class TransformationJobTest { final MetadataRecord mr = new MetadataRecord(); mr.setBody(IOUtils.toString(getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/input.xml"))); - - // We Load the XSLT trasformation Rule from the classpath + // We Load the XSLT transformation Rule from the classpath XSLTTransformationFunction tr = loadTransformationRule("/eu/dnetlib/dhp/transform/ext_simple.xsl"); - //Print the record + // Print the record System.out.println(tr.call(mr).getBody()); - //TODO Create significant Assert + // TODO Create significant Assert } - - - @DisplayName("Test TransformSparkJobNode.main") @Test public void transformTest(@TempDir Path testDir) throws Exception { @@ -96,24 +98,44 @@ public class TransformationJobTest { final String mdstore_input = this.getClass().getResource("/eu/dnetlib/dhp/transform/mdstorenative").getFile(); final String mdstore_output = testDir.toString() + "/version"; - - mockupTrasformationRule("simpleTRule","/eu/dnetlib/dhp/transform/ext_simple.xsl"); + mockupTrasformationRule("simpleTRule", "/eu/dnetlib/dhp/transform/ext_simple.xsl"); // final String arguments = "-issm true -i %s -o %s -d 1 -w 1 -tp XSLT_TRANSFORM -tr simpleTRule"; - final Map parameters = Stream.of(new String[][] { - { "dateOfTransformation", "1234" }, - { "transformationPlugin", "XSLT_TRANSFORM" }, - { "transformationRule", "simpleTRule" }, + final Map parameters = Stream.of(new String[][] { + { + "dateOfTransformation", "1234" + }, + { + "transformationPlugin", "XSLT_TRANSFORM" + }, + { + "transformationRuleTitle", "simpleTRule" + }, }).collect(Collectors.toMap(data -> data[0], data -> data[1])); - TransformSparkJobNode.transformRecords(parameters,isLookUpService,spark,mdstore_input, mdstore_output); - - - + TransformSparkJobNode.transformRecords(parameters, isLookUpService, spark, mdstore_input, mdstore_output); // TODO introduce useful assertions + + final Encoder encoder = Encoders.bean(MetadataRecord.class); + final Dataset mOutput = spark.read().format("parquet").load(mdstore_output).as(encoder); + + final Long total = mOutput.count(); + + final long recordTs = mOutput + .filter((FilterFunction) p -> p.getDateOfTransformation() == 1234) + .count(); + + final long recordNotEmpty = mOutput + .filter((FilterFunction) p -> !StringUtils.isBlank(p.getBody())) + .count(); + + assertEquals(total, recordTs); + + assertEquals(total, recordNotEmpty); + } @Test @@ -128,27 +150,27 @@ public class TransformationJobTest { Files.deleteIfExists(tempDirWithPrefix); } - - private void mockupTrasformationRule(final String trule, final String path)throws Exception { + private void mockupTrasformationRule(final String trule, final String path) throws Exception { final String trValue = IOUtils.toString(this.getClass().getResourceAsStream(path)); - lenient().when(isLookUpService.quickSearchProfile(String.format(TransformationFactory.TRULE_XQUERY,trule))) - .thenReturn(Collections.singletonList(trValue)); + lenient() + .when(isLookUpService.quickSearchProfile(String.format(TransformationFactory.TRULE_XQUERY, trule))) + .thenReturn(Collections.singletonList(trValue)); } private XSLTTransformationFunction loadTransformationRule(final String path) throws Exception { final String trValue = IOUtils.toString(this.getClass().getResourceAsStream(path)); final LongAccumulator la = new LongAccumulator(); - return new XSLTTransformationFunction(new AggregationCounter(la,la,la),trValue, 0,vocabularies); + return new XSLTTransformationFunction(new AggregationCounter(la, la, la), trValue, 0, vocabularies); } private List vocs() throws IOException { return IOUtils - .readLines(TransformationJobTest.class.getResourceAsStream("/eu/dnetlib/dhp/transform/terms.txt")); + .readLines(TransformationJobTest.class.getResourceAsStream("/eu/dnetlib/dhp/transform/terms.txt")); } private List synonyms() throws IOException { return IOUtils - .readLines(TransformationJobTest.class.getResourceAsStream("/eu/dnetlib/dhp/transform/synonyms.txt")); + .readLines(TransformationJobTest.class.getResourceAsStream("/eu/dnetlib/dhp/transform/synonyms.txt")); } } diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctions.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctions.java index 42ce7f90b..ac483f10b 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctions.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctions.java @@ -59,17 +59,19 @@ public class CleaningFunctions { } } if (Objects.nonNull(r.getAuthor())) { - r.getAuthor() - .stream() - .filter(Objects::nonNull) - .forEach(a -> { - if (Objects.nonNull(a.getPid())) { - a.getPid() - .stream() - .filter(Objects::nonNull) - .forEach(p -> fixVocabName(p.getQualifier(), ModelConstants.DNET_PID_TYPES)); - } - }); + r + .getAuthor() + .stream() + .filter(Objects::nonNull) + .forEach(a -> { + if (Objects.nonNull(a.getPid())) { + a + .getPid() + .stream() + .filter(Objects::nonNull) + .forEach(p -> fixVocabName(p.getQualifier(), ModelConstants.DNET_PID_TYPES)); + } + }); } if (value instanceof Publication) { diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java index db1a2ef57..7ff06e428 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java @@ -55,9 +55,9 @@ import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.common.DbClient; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.oa.graph.raw.common.AbstractMigrationApplication; import eu.dnetlib.dhp.oa.graph.raw.common.VerifyNsPrefixPredicate; -import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.schema.oaf.Context; import eu.dnetlib.dhp.schema.oaf.DataInfo; import eu.dnetlib.dhp.schema.oaf.Dataset; diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java index 8293faac4..83303ae8e 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/GenerateEntitiesApplicationTest.java @@ -15,8 +15,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import eu.dnetlib.dhp.oa.graph.clean.CleaningFunctionTest; import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; +import eu.dnetlib.dhp.oa.graph.clean.CleaningFunctionTest; import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java index 5c8e4e4c6..e54fe28aa 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java @@ -21,8 +21,8 @@ import org.mockito.junit.jupiter.MockitoExtension; import com.fasterxml.jackson.databind.ObjectMapper; -import eu.dnetlib.dhp.oa.graph.clean.CleaningFunctionTest; import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; +import eu.dnetlib.dhp.oa.graph.clean.CleaningFunctionTest; import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.Author; import eu.dnetlib.dhp.schema.oaf.Dataset; From 99cf3a8ea4980de25f7030acc398ac751ac165ea Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Thu, 28 Jan 2021 16:34:46 +0100 Subject: [PATCH 070/445] Merged Datacite transfrom into this branch --- .../dhp/schema/scholexplorer/OafUtils.scala | 4 +- dhp-workflows/dhp-aggregation/pom.xml | 36 +- .../datacite/AbstractRestClient.scala | 73 ++ .../datacite/DataciteAPIImporter.scala | 25 + .../DataciteToOAFTransformation.scala | 475 ++++++++ .../datacite/ExportActionSetJobNode.scala | 41 + .../GenerateDataciteDatasetSpark.scala | 48 + .../datacite/ImportDatacite.scala | 168 +++ .../actionmanager/datacite/datacite_filter | 28 + .../datacite/exportDataset_parameters.json | 21 + .../datacite/generate_dataset_params.json | 33 + .../actionmanager/datacite/hostedBy_map.json | 1032 +++++++++++++++++ .../datacite/import_from_api.json | 27 + .../datacite/oozie_app/config-default.xml | 18 + .../datacite/oozie_app/workflow.xml | 103 ++ .../doiboost/DoiBoostMappingUtil.scala | 4 +- .../java/eu/dnetlib/dhp/export/DLIToOAF.scala | 2 +- 17 files changed, 2132 insertions(+), 6 deletions(-) create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/AbstractRestClient.scala create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteAPIImporter.scala create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ExportActionSetJobNode.scala create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/GenerateDataciteDatasetSpark.scala create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ImportDatacite.scala create mode 100644 dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/datacite_filter create mode 100644 dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/exportDataset_parameters.json create mode 100644 dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/generate_dataset_params.json create mode 100644 dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/hostedBy_map.json create mode 100644 dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/import_from_api.json create mode 100644 dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/config-default.xml create mode 100644 dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/workflow.xml diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/scholexplorer/OafUtils.scala b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/scholexplorer/OafUtils.scala index 27eec77fa..526d65782 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/scholexplorer/OafUtils.scala +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/scholexplorer/OafUtils.scala @@ -15,11 +15,11 @@ object OafUtils { } - def generateDataInfo(trust: String = "0.9", invisibile: Boolean = false): DataInfo = { + def generateDataInfo(trust: String = "0.9", invisible: Boolean = false): DataInfo = { val di = new DataInfo di.setDeletedbyinference(false) di.setInferred(false) - di.setInvisible(false) + di.setInvisible(invisible) di.setTrust(trust) di.setProvenanceaction(createQualifier("sysimport:actionset", "dnet:provenanceActions")) di diff --git a/dhp-workflows/dhp-aggregation/pom.xml b/dhp-workflows/dhp-aggregation/pom.xml index cf0fa0efe..0445e0e1b 100644 --- a/dhp-workflows/dhp-aggregation/pom.xml +++ b/dhp-workflows/dhp-aggregation/pom.xml @@ -7,10 +7,44 @@ 1.2.4-SNAPSHOT dhp-aggregation - + + + + net.alchim31.maven + scala-maven-plugin + ${net.alchim31.maven.version} + + + scala-compile-first + initialize + + add-source + compile + + + + scala-test-compile + process-test-resources + + testCompile + + + + + ${scala.version} + + + + + + + org.apache.httpcomponents + httpclient + + org.apache.spark spark-core_2.11 diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/AbstractRestClient.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/AbstractRestClient.scala new file mode 100644 index 000000000..852147ccd --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/AbstractRestClient.scala @@ -0,0 +1,73 @@ +package eu.dnetlib.dhp.actionmanager.datacite + +import org.apache.commons.io.IOUtils +import org.apache.http.client.methods.{HttpGet, HttpPost, HttpRequestBase, HttpUriRequest} +import org.apache.http.entity.StringEntity +import org.apache.http.impl.client.HttpClients + +import java.io.IOException + +abstract class AbstractRestClient extends Iterator[String]{ + + var buffer: List[String] = List() + var current_index:Int = 0 + + var scroll_value: Option[String] = None + + var complete:Boolean = false + + + def extractInfo(input: String): Unit + + protected def getBufferData(): Unit + + + def doHTTPGETRequest(url:String): String = { + val httpGet = new HttpGet(url) + doHTTPRequest(httpGet) + + } + + def doHTTPPOSTRequest(url:String, json:String): String = { + val httpPost = new HttpPost(url) + if (json != null) { + val entity = new StringEntity(json) + httpPost.setEntity(entity) + httpPost.setHeader("Accept", "application/json") + httpPost.setHeader("Content-type", "application/json") + } + doHTTPRequest(httpPost) + } + + def hasNext: Boolean = { + buffer.nonEmpty && current_index < buffer.size + } + + + override def next(): String = { + val next_item:String = buffer(current_index) + current_index = current_index + 1 + if (current_index == buffer.size) + getBufferData() + next_item + } + + + private def doHTTPRequest[A <: HttpUriRequest](r: A) :String ={ + val client = HttpClients.createDefault + try { + val response = client.execute(r) + IOUtils.toString(response.getEntity.getContent) + } catch { + case e: Throwable => + throw new RuntimeException("Error on executing request ", e) + } finally try client.close() + catch { + case e: IOException => + throw new RuntimeException("Unable to close client ", e) + } + } + + getBufferData() + +} \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteAPIImporter.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteAPIImporter.scala new file mode 100644 index 000000000..c2ad6855c --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteAPIImporter.scala @@ -0,0 +1,25 @@ +package eu.dnetlib.dhp.actionmanager.datacite + +import org.json4s.{DefaultFormats, JValue} +import org.json4s.jackson.JsonMethods.{compact, parse, render} + +class DataciteAPIImporter(timestamp: Long = 0, blocks: Long = 10) extends AbstractRestClient { + + override def extractInfo(input: String): Unit = { + implicit lazy val formats: DefaultFormats.type = org.json4s.DefaultFormats + lazy val json: org.json4s.JValue = parse(input) + buffer = (json \ "data").extract[List[JValue]].map(s => compact(render(s))) + val next_url = (json \ "links" \ "next").extractOrElse[String](null) + scroll_value = if (next_url != null && next_url.nonEmpty) Some(next_url) else None + if (scroll_value.isEmpty) + complete = true + current_index = 0 + } + + override def getBufferData(): Unit = { + if (!complete) { + val response = if (scroll_value.isDefined) doHTTPGETRequest(scroll_value.get) else doHTTPGETRequest(s"https://api.datacite.org/dois?page[cursor]=1&page[size]=$blocks&query=updated:[$timestamp%20TO%20*]") + extractInfo(response) + } + } +} \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala new file mode 100644 index 000000000..9418e71da --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala @@ -0,0 +1,475 @@ +package eu.dnetlib.dhp.actionmanager.datacite + +import com.fasterxml.jackson.databind.ObjectMapper +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup +import eu.dnetlib.dhp.schema.action.AtomicAction +import eu.dnetlib.dhp.schema.oaf.{Author, DataInfo, Instance, KeyValue, Oaf, OafMapperUtils, OtherResearchProduct, Publication, Qualifier, Relation, Result, Software, StructuredProperty, Dataset => OafDataset} +import eu.dnetlib.dhp.utils.DHPUtils +import org.apache.commons.lang3.StringUtils +import org.json4s.DefaultFormats +import org.json4s.JsonAST.{JField, JObject, JString} +import org.json4s.jackson.JsonMethods.parse + +import java.nio.charset.CodingErrorAction +import java.time.LocalDate +import java.time.format.DateTimeFormatter +import java.util.Locale +import java.util.regex.Pattern +import scala.collection.JavaConverters._ +import scala.io.{Codec, Source} + + + +case class DataciteType(doi:String,timestamp:Long,isActive:Boolean, json:String ){} + +case class NameIdentifiersType(nameIdentifierScheme: Option[String], schemeUri: Option[String], nameIdentifier: Option[String]) {} + +case class CreatorType(nameType: Option[String], nameIdentifiers: Option[List[NameIdentifiersType]], name: Option[String], familyName: Option[String], givenName: Option[String], affiliation: Option[List[String]]) {} + +case class TitleType(title: Option[String], titleType: Option[String], lang: Option[String]) {} + +case class SubjectType(subject: Option[String], subjectScheme: Option[String]) {} + +case class DescriptionType(descriptionType: Option[String], description: Option[String]) {} + +case class FundingReferenceType(funderIdentifierType: Option[String], awardTitle: Option[String], awardUri: Option[String], funderName: Option[String], funderIdentifier: Option[String], awardNumber: Option[String]) {} + +case class DateType(date: Option[String], dateType: Option[String]) {} + +case class HostedByMapType(openaire_id: String, datacite_name: String, official_name: String, similarity: Option[Float]) {} + +object DataciteToOAFTransformation { + + implicit val codec: Codec = Codec("UTF-8") + codec.onMalformedInput(CodingErrorAction.REPLACE) + codec.onUnmappableCharacter(CodingErrorAction.REPLACE) + + private val PID_VOCABULARY = "dnet:pid_types" + val COBJ_VOCABULARY = "dnet:publication_resource" + val RESULT_VOCABULARY = "dnet:result_typologies" + val ACCESS_MODE_VOCABULARY = "dnet:access_modes" + val DOI_CLASS = "doi" + + val TITLE_SCHEME = "dnet:dataCite_title" + val SUBJ_CLASS = "keywords" + val SUBJ_SCHEME = "dnet:subject_classification_typologies" + + val j_filter:List[String] = { + val s = Source.fromInputStream(getClass.getResourceAsStream("datacite_filter")).mkString + s.lines.toList + } + + val mapper = new ObjectMapper() + val unknown_repository: HostedByMapType = HostedByMapType("openaire____::1256f046-bf1f-4afc-8b47-d0b147148b18", "Unknown Repository", "Unknown Repository", Some(1.0F)) + + val dataInfo: DataInfo = generateDataInfo("0.9") + val DATACITE_COLLECTED_FROM: KeyValue = OafMapperUtils.keyValue("openaire____::datacite", "Datacite") + + val hostedByMap: Map[String, HostedByMapType] = { + val s = Source.fromInputStream(getClass.getResourceAsStream("hostedBy_map.json")).mkString + implicit lazy val formats: DefaultFormats.type = org.json4s.DefaultFormats + lazy val json: org.json4s.JValue = parse(s) + json.extract[Map[String, HostedByMapType]] + } + + val df_en: DateTimeFormatter = DateTimeFormatter.ofPattern("[MM-dd-yyyy][MM/dd/yyyy][dd-MM-yy][dd-MMM-yyyy][dd/MMM/yyyy][dd-MMM-yy][dd/MMM/yy][dd-MM-yy][dd/MM/yy][dd-MM-yyyy][dd/MM/yyyy][yyyy-MM-dd][yyyy/MM/dd]", Locale.ENGLISH) + val df_it: DateTimeFormatter = DateTimeFormatter.ofPattern("[dd-MM-yyyy][dd/MM/yyyy]", Locale.ITALIAN) + + val funder_regex:List[(Pattern, String)] = List( + (Pattern.compile("(info:eu-repo/grantagreement/ec/h2020/)(\\d\\d\\d\\d\\d\\d)(.*)", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE),"40|corda__h2020::"), + (Pattern.compile("(info:eu-repo/grantagreement/ec/fp7/)(\\d\\d\\d\\d\\d\\d)(.*)", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE),"40|corda_______::") + + ) + + val Date_regex: List[Pattern] = List( + //Y-M-D + Pattern.compile("(18|19|20)\\d\\d([- /.])(0[1-9]|1[012])\\2(0[1-9]|[12][0-9]|3[01])", Pattern.MULTILINE), + //M-D-Y + Pattern.compile("((0[1-9]|1[012])|([1-9]))([- /.])(0[1-9]|[12][0-9]|3[01])([- /.])(18|19|20)?\\d\\d", Pattern.MULTILINE), + //D-M-Y + Pattern.compile("(?:(?:31(/|-|\\.)(?:0?[13578]|1[02]|(?:Jan|Mar|May|Jul|Aug|Oct|Dec)))\\1|(?:(?:29|30)(/|-|\\.)(?:0?[1,3-9]|1[0-2]|(?:Jan|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec))\\2))(?:(?:1[6-9]|[2-9]\\d)?\\d{2})|(?:29(/|-|\\.)(?:0?2|(?:Feb))\\3(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))|(?:0?[1-9]|1\\d|2[0-8])(/|-|\\.)(?:(?:0?[1-9]|(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep))|(?:1[0-2]|(?:Oct|Nov|Dec)))\\4(?:(?:1[6-9]|[2-9]\\d)?\\d{2})", Pattern.MULTILINE), + //Y + Pattern.compile("(19|20)\\d\\d", Pattern.MULTILINE) + ) + + + def filter_json(json:String):Boolean = { + j_filter.exists(f => json.contains(f)) + } + + def toActionSet(item:Oaf) :(String, String) = { + val mapper = new ObjectMapper() + + item match { + case dataset: OafDataset => + val a: AtomicAction[OafDataset] = new AtomicAction[OafDataset] + a.setClazz(classOf[OafDataset]) + a.setPayload(dataset) + (dataset.getClass.getCanonicalName, mapper.writeValueAsString(a)) + case publication: Publication => + val a: AtomicAction[Publication] = new AtomicAction[Publication] + a.setClazz(classOf[Publication]) + a.setPayload(publication) + (publication.getClass.getCanonicalName, mapper.writeValueAsString(a)) + case software: Software => + val a: AtomicAction[Software] = new AtomicAction[Software] + a.setClazz(classOf[Software]) + a.setPayload(software) + (software.getClass.getCanonicalName, mapper.writeValueAsString(a)) + case orp: OtherResearchProduct => + val a: AtomicAction[OtherResearchProduct] = new AtomicAction[OtherResearchProduct] + a.setClazz(classOf[OtherResearchProduct]) + a.setPayload(orp) + (orp.getClass.getCanonicalName, mapper.writeValueAsString(a)) + + case relation: Relation => + val a: AtomicAction[Relation] = new AtomicAction[Relation] + a.setClazz(classOf[Relation]) + a.setPayload(relation) + (relation.getClass.getCanonicalName, mapper.writeValueAsString(a)) + case _ => + null + } + + } + + + + + def embargo_end(embargo_end_date: String): Boolean = { + val dt = LocalDate.parse(embargo_end_date, DateTimeFormatter.ofPattern("[yyyy-MM-dd]")) + val td = LocalDate.now() + td.isAfter(dt) + } + + + def extract_date(input: String): Option[String] = { + val d = Date_regex.map(pattern => { + val matcher = pattern.matcher(input) + if (matcher.find()) + matcher.group(0) + else + null + } + ).find(s => s != null) + + if (d.isDefined) { + val a_date = if (d.get.length == 4) s"01-01-${d.get}" else d.get + try { + return Some(LocalDate.parse(a_date, df_en).toString) + } catch { + case _: Throwable => try { + return Some(LocalDate.parse(a_date, df_it).toString) + } catch { + case _: Throwable => try { + return None + } + } + } + } + d + } + + def getTypeQualifier(resourceType: String, resourceTypeGeneral: String, schemaOrg: String, vocabularies:VocabularyGroup): (Qualifier, Qualifier) = { + if (resourceType != null && resourceType.nonEmpty) { + val typeQualifier = vocabularies.getSynonymAsQualifier(COBJ_VOCABULARY, resourceType) + if (typeQualifier != null) + return (typeQualifier, vocabularies.getSynonymAsQualifier(RESULT_VOCABULARY, typeQualifier.getClassid)) + } + if (schemaOrg != null && schemaOrg.nonEmpty) { + val typeQualifier = vocabularies.getSynonymAsQualifier(COBJ_VOCABULARY, schemaOrg) + if (typeQualifier != null) + return (typeQualifier, vocabularies.getSynonymAsQualifier(RESULT_VOCABULARY, typeQualifier.getClassid)) + + } + if (resourceTypeGeneral != null && resourceTypeGeneral.nonEmpty) { + val typeQualifier = vocabularies.getSynonymAsQualifier(COBJ_VOCABULARY, resourceTypeGeneral) + if (typeQualifier != null) + return (typeQualifier, vocabularies.getSynonymAsQualifier(RESULT_VOCABULARY, typeQualifier.getClassid)) + + } + null + } + + + def getResult(resourceType: String, resourceTypeGeneral: String, schemaOrg: String, vocabularies:VocabularyGroup): Result = { + val typeQualifiers: (Qualifier, Qualifier) = getTypeQualifier(resourceType, resourceTypeGeneral, schemaOrg, vocabularies) + if (typeQualifiers == null) + return null + val i = new Instance + i.setInstancetype(typeQualifiers._1) + typeQualifiers._2.getClassname match { + case "dataset" => + val r = new OafDataset + r.setInstance(List(i).asJava) + return r + case "publication" => + val r = new Publication + r.setInstance(List(i).asJava) + return r + case "software" => + val r = new Software + r.setInstance(List(i).asJava) + return r + case "other" => + val r = new OtherResearchProduct + r.setInstance(List(i).asJava) + return r + } + null + } + + + def available_date(input: String): Boolean = { + + implicit lazy val formats: DefaultFormats.type = org.json4s.DefaultFormats + lazy val json: org.json4s.JValue = parse(input) + val l: List[String] = for { + JObject(dates) <- json \\ "dates" + JField("dateType", JString(dateTypes)) <- dates + } yield dateTypes + + l.exists(p => p.equalsIgnoreCase("available")) + + } + + + def generateOAFDate(dt: String, q: Qualifier): StructuredProperty = { + OafMapperUtils.structuredProperty(dt, q, null) + } + + def generateRelation(sourceId:String, targetId:String, relClass:String, cf:KeyValue, di:DataInfo) :Relation = { + + val r = new Relation + r.setSource(sourceId) + r.setTarget(targetId) + r.setRelType("resultProject") + r.setRelClass(relClass) + r.setSubRelType("outcome") + r.setCollectedfrom(List(cf).asJava) + r.setDataInfo(di) + r + + + } + + def get_projectRelation(awardUri:String, sourceId:String):List[Relation] = { + val match_pattern = funder_regex.find(s =>s._1.matcher(awardUri).find()) + + if (match_pattern.isDefined) { + val m =match_pattern.get._1 + val p = match_pattern.get._2 + val grantId = m.matcher(awardUri).replaceAll("$2") + val targetId = s"$p${DHPUtils.md5(grantId)}" + List( + generateRelation(sourceId, targetId,"isProducedBy", DATACITE_COLLECTED_FROM, dataInfo), + generateRelation(targetId, sourceId,"produces", DATACITE_COLLECTED_FROM, dataInfo) + ) + } + else + List() + + } + + + def generateOAF(input:String,ts:Long, dateOfCollection:Long, vocabularies: VocabularyGroup):List[Oaf] = { + if (filter_json(input)) + return List() + + implicit lazy val formats: DefaultFormats.type = org.json4s.DefaultFormats + lazy val json = parse(input) + + val resourceType = (json \ "attributes" \ "types" \ "resourceType").extractOrElse[String](null) + val resourceTypeGeneral = (json \ "attributes" \ "types" \ "resourceTypeGeneral").extractOrElse[String](null) + val schemaOrg = (json \ "attributes" \ "types" \ "schemaOrg").extractOrElse[String](null) + + val doi = (json \ "attributes" \ "doi").extract[String] + if (doi.isEmpty) + return List() + + //Mapping type based on vocabularies dnet:publication_resource and dnet:result_typologies + val result = getResult(resourceType, resourceTypeGeneral, schemaOrg, vocabularies) + if (result == null) + return List() + + + val doi_q = vocabularies.getSynonymAsQualifier(PID_VOCABULARY, "doi") + val pid = OafMapperUtils.structuredProperty(doi, doi_q, dataInfo) + result.setPid(List(pid).asJava) + result.setId(OafMapperUtils.createOpenaireId(50, s"datacite____::$doi", true)) + result.setOriginalId(List(doi).asJava) + result.setDateofcollection(s"${dateOfCollection}") + result.setDateoftransformation(s"$ts") + result.setDataInfo(dataInfo) + + val creators = (json \\ "creators").extractOrElse[List[CreatorType]](List()) + + + val authors = creators.zipWithIndex.map { case (c, idx) => + val a = new Author + a.setFullname(c.name.orNull) + a.setName(c.givenName.orNull) + a.setSurname(c.familyName.orNull) + if (c.nameIdentifiers!= null&& c.nameIdentifiers.isDefined && c.nameIdentifiers.get != null) { + a.setPid(c.nameIdentifiers.get.map(ni => { + val q = if (ni.nameIdentifierScheme.isDefined) vocabularies.getTermAsQualifier(PID_VOCABULARY, ni.nameIdentifierScheme.get.toLowerCase()) else null + if (ni.nameIdentifier!= null && ni.nameIdentifier.isDefined) { + OafMapperUtils.structuredProperty(ni.nameIdentifier.get, q, dataInfo) + } + else + null + + } + ) + .asJava) + } + if (c.affiliation.isDefined) + a.setAffiliation(c.affiliation.get.filter(af => af.nonEmpty).map(af => OafMapperUtils.field(af, dataInfo)).asJava) + a.setRank(idx + 1) + a + } + + + + + val titles:List[TitleType] = (json \\ "titles").extractOrElse[List[TitleType]](List()) + + result.setTitle(titles.filter(t => t.title.nonEmpty).map(t => { + if (t.titleType.isEmpty) { + OafMapperUtils.structuredProperty(t.title.get, "main title", "main title", TITLE_SCHEME, TITLE_SCHEME, null) + } else { + OafMapperUtils.structuredProperty(t.title.get, t.titleType.get, t.titleType.get, TITLE_SCHEME, TITLE_SCHEME, null) + } + }).asJava) + + if(authors==null || authors.isEmpty || !authors.exists(a => a !=null)) + return List() + result.setAuthor(authors.asJava) + + val dates = (json \\ "dates").extract[List[DateType]] + val publication_year = (json \\ "publicationYear").extractOrElse[String](null) + + val i_date = dates + .filter(d => d.date.isDefined && d.dateType.isDefined) + .find(d => d.dateType.get.equalsIgnoreCase("issued")) + .map(d => extract_date(d.date.get)) + val a_date: Option[String] = dates + .filter(d => d.date.isDefined && d.dateType.isDefined && d.dateType.get.equalsIgnoreCase("available")) + .map(d => extract_date(d.date.get)) + .find(d => d != null && d.isDefined) + .map(d => d.get) + + if (a_date.isDefined) { + result.setEmbargoenddate(OafMapperUtils.field(a_date.get, null)) + } + if (i_date.isDefined && i_date.get.isDefined) { + result.setDateofacceptance(OafMapperUtils.field(i_date.get.get, null)) + result.getInstance().get(0).setDateofacceptance(OafMapperUtils.field(i_date.get.get, null)) + } + else if (publication_year != null) { + result.setDateofacceptance(OafMapperUtils.field(s"01-01-$publication_year", null)) + result.getInstance().get(0).setDateofacceptance(OafMapperUtils.field(s"01-01-$publication_year", null)) + } + + + result.setRelevantdate(dates.filter(d => d.date.isDefined && d.dateType.isDefined) + .map(d => (extract_date(d.date.get), d.dateType.get)) + .filter(d => d._1.isDefined) + .map(d => (d._1.get, vocabularies.getTermAsQualifier("dnet:dataCite_date", d._2.toLowerCase()))) + .filter(d => d._2 != null) + .map(d => generateOAFDate(d._1, d._2)).asJava) + + val subjects = (json \\ "subjects").extract[List[SubjectType]] + + result.setSubject(subjects.filter(s => s.subject.nonEmpty) + .map(s => + OafMapperUtils.structuredProperty(s.subject.get, SUBJ_CLASS, SUBJ_CLASS, SUBJ_SCHEME, SUBJ_SCHEME, null) + ).asJava) + + + result.setCollectedfrom(List(DATACITE_COLLECTED_FROM).asJava) + + val descriptions = (json \\ "descriptions").extract[List[DescriptionType]] + + result.setDescription( + descriptions + .filter(d => d.description.isDefined). + map(d => + OafMapperUtils.field(d.description.get, null) + ).filter(s => s!=null).asJava) + + + val publisher = (json \\ "publisher").extractOrElse[String](null) + if (publisher != null) + result.setPublisher(OafMapperUtils.field(publisher, null)) + + + val language: String = (json \\ "language").extractOrElse[String](null) + + if (language != null) + result.setLanguage(vocabularies.getSynonymAsQualifier("dnet:languages", language)) + + + val instance = result.getInstance().get(0) + + val client = (json \ "relationships" \ "client" \\ "id").extractOpt[String] + + val accessRights:List[String] = for { + JObject(rightsList) <- json \\ "rightsList" + JField("rightsUri", JString(rightsUri)) <- rightsList + } yield rightsUri + + val aRights: Option[Qualifier] = accessRights.map(r => { + vocabularies.getSynonymAsQualifier(ACCESS_MODE_VOCABULARY, r) + }).find(q => q != null) + + + val access_rights_qualifier = if (aRights.isDefined) aRights.get else OafMapperUtils.qualifier("UNKNOWN", "not available", ACCESS_MODE_VOCABULARY, ACCESS_MODE_VOCABULARY) + + if (client.isDefined) { + val hb = hostedByMap.getOrElse(client.get.toUpperCase(), unknown_repository) + instance.setHostedby(OafMapperUtils.keyValue(generateDSId(hb.openaire_id), hb.official_name)) + instance.setCollectedfrom(DATACITE_COLLECTED_FROM) + instance.setUrl(List(s"https://dx.doi.org/$doi").asJava) + instance.setAccessright(access_rights_qualifier) + + //'http') and matches(., '.*(/licenses|/publicdomain|unlicense.org/|/legal-and-data-protection-notices|/download/license|/open-government-licence).*')]"> + val license = accessRights + .find(r => r.startsWith("http") && r.matches(".*(/licenses|/publicdomain|unlicense\\.org/|/legal-and-data-protection-notices|/download/license|/open-government-licence).*")) + if (license.isDefined) + instance.setLicense(OafMapperUtils.field(license.get, null)) + } + + + val awardUris:List[String] = for { + JObject(fundingReferences) <- json \\ "fundingReferences" + JField("awardUri", JString(awardUri)) <- fundingReferences + } yield awardUri + + val relations:List[Relation] =awardUris.flatMap(a=> get_projectRelation(a, result.getId)).filter(r => r!= null) + + if (relations!= null && relations.nonEmpty) { + List(result):::relations + } + else + List(result) + } + + def generateDataInfo(trust: String): DataInfo = { + val di = new DataInfo + di.setDeletedbyinference(false) + di.setInferred(false) + di.setInvisible(false) + di.setTrust(trust) + di.setProvenanceaction(OafMapperUtils.qualifier("sysimport:actionset", "sysimport:actionset", "dnet:provenanceActions", "dnet:provenanceActions")) + di + } + + def generateDSId(input: String): String = { + val b = StringUtils.substringBefore(input, "::") + val a = StringUtils.substringAfter(input, "::") + s"10|$b::${DHPUtils.md5(a)}" + } + + +} \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ExportActionSetJobNode.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ExportActionSetJobNode.scala new file mode 100644 index 000000000..9f0d25735 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ExportActionSetJobNode.scala @@ -0,0 +1,41 @@ +package eu.dnetlib.dhp.actionmanager.datacite + +import eu.dnetlib.dhp.application.ArgumentApplicationParser +import eu.dnetlib.dhp.schema.oaf.Oaf +import org.apache.hadoop.io.Text +import org.apache.hadoop.io.compress.GzipCodec +import org.apache.hadoop.mapred.SequenceFileOutputFormat +import org.apache.spark.SparkConf +import org.apache.spark.sql.{Dataset, Encoder, Encoders, SaveMode, SparkSession} +import org.slf4j.{Logger, LoggerFactory} + +import scala.io.Source + +object ExportActionSetJobNode { + + val log: Logger = LoggerFactory.getLogger(ExportActionSetJobNode.getClass) + + def main(args: Array[String]): Unit = { + val conf = new SparkConf + val parser = new ArgumentApplicationParser(Source.fromInputStream(getClass.getResourceAsStream("/eu/dnetlib/dhp/actionmanager/datacite/exportDataset_parameters.json")).mkString) + parser.parseArgument(args) + val master = parser.get("master") + val sourcePath = parser.get("sourcePath") + val targetPath = parser.get("targetPath") + + val spark: SparkSession = SparkSession.builder().config(conf) + .appName(ExportActionSetJobNode.getClass.getSimpleName) + .master(master) + .getOrCreate() + implicit val resEncoder: Encoder[Oaf] = Encoders.kryo[Oaf] + implicit val tEncoder:Encoder[(String,String)] = Encoders.tuple(Encoders.STRING,Encoders.STRING) + + spark.read.load(sourcePath).as[Oaf] + .map(o =>DataciteToOAFTransformation.toActionSet(o)) + .filter(o => o!= null) + .rdd.map(s => (new Text(s._1), new Text(s._2))).saveAsHadoopFile(s"$targetPath", classOf[Text], classOf[Text], classOf[SequenceFileOutputFormat[Text,Text]], classOf[GzipCodec]) + + + } + +} \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/GenerateDataciteDatasetSpark.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/GenerateDataciteDatasetSpark.scala new file mode 100644 index 000000000..6837e94b2 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/GenerateDataciteDatasetSpark.scala @@ -0,0 +1,48 @@ +package eu.dnetlib.dhp.actionmanager.datacite + +import eu.dnetlib.dhp.application.ArgumentApplicationParser +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup +import eu.dnetlib.dhp.model.mdstore.MetadataRecord +import eu.dnetlib.dhp.schema.oaf.Oaf +import eu.dnetlib.dhp.utils.ISLookupClientFactory +import org.apache.spark.SparkConf +import org.apache.spark.sql.{Encoder, Encoders, SaveMode, SparkSession} +import org.slf4j.{Logger, LoggerFactory} + +import scala.io.Source + +object GenerateDataciteDatasetSpark { + + val log: Logger = LoggerFactory.getLogger(GenerateDataciteDatasetSpark.getClass) + + def main(args: Array[String]): Unit = { + val conf = new SparkConf + val parser = new ArgumentApplicationParser(Source.fromInputStream(getClass.getResourceAsStream("/eu/dnetlib/dhp/actionmanager/datacite/generate_dataset_params.json")).mkString) + parser.parseArgument(args) + val master = parser.get("master") + val sourcePath = parser.get("sourcePath") + val targetPath = parser.get("targetPath") + val isLookupUrl: String = parser.get("isLookupUrl") + log.info("isLookupUrl: {}", isLookupUrl) + + val isLookupService = ISLookupClientFactory.getLookUpService(isLookupUrl) + val vocabularies = VocabularyGroup.loadVocsFromIS(isLookupService) + + val spark: SparkSession = SparkSession.builder().config(conf) + .appName(GenerateDataciteDatasetSpark.getClass.getSimpleName) + .master(master) + .getOrCreate() + + implicit val mrEncoder: Encoder[MetadataRecord] = Encoders.kryo[MetadataRecord] + + implicit val resEncoder: Encoder[Oaf] = Encoders.kryo[Oaf] + + import spark.implicits._ + + spark.read.load(sourcePath).as[DataciteType] + .filter(d => d.isActive) + .flatMap(d => DataciteToOAFTransformation.generateOAF(d.json, d.timestamp, d.timestamp, vocabularies)) + .filter(d => d != null) + .write.mode(SaveMode.Overwrite).save(targetPath) + } +} \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ImportDatacite.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ImportDatacite.scala new file mode 100644 index 000000000..06fcbb518 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ImportDatacite.scala @@ -0,0 +1,168 @@ +package eu.dnetlib.dhp.actionmanager.datacite + +import eu.dnetlib.dhp.application.ArgumentApplicationParser +import org.apache.hadoop.conf.Configuration +import org.apache.hadoop.fs.{FileSystem, LocalFileSystem, Path} +import org.apache.hadoop.hdfs.DistributedFileSystem +import org.apache.hadoop.io.{IntWritable, SequenceFile, Text} +import org.apache.spark.SparkContext +import org.apache.spark.rdd.RDD +import org.apache.spark.sql.expressions.Aggregator +import org.apache.spark.sql.{Dataset, Encoder, SaveMode, SparkSession} +import org.json4s.DefaultFormats +import org.json4s.jackson.JsonMethods.parse +import org.apache.spark.sql.functions.max +import org.slf4j.{Logger, LoggerFactory} + +import java.time.format.DateTimeFormatter._ +import java.time.{LocalDateTime, ZoneOffset} +import scala.io.Source + +object ImportDatacite { + + val log: Logger = LoggerFactory.getLogger(ImportDatacite.getClass) + + + def convertAPIStringToDataciteItem(input:String): DataciteType = { + implicit lazy val formats: DefaultFormats.type = org.json4s.DefaultFormats + lazy val json: org.json4s.JValue = parse(input) + val doi = (json \ "attributes" \ "doi").extract[String].toLowerCase + + val isActive = (json \ "attributes" \ "isActive").extract[Boolean] + + val timestamp_string = (json \ "attributes" \ "updated").extract[String] + val dt = LocalDateTime.parse(timestamp_string, ISO_DATE_TIME) + DataciteType(doi = doi, timestamp = dt.toInstant(ZoneOffset.UTC).toEpochMilli/1000, isActive = isActive, json = input) + + } + + + + def main(args: Array[String]): Unit = { + + val parser = new ArgumentApplicationParser(Source.fromInputStream(getClass.getResourceAsStream("/eu/dnetlib/dhp/actionmanager/datacite/import_from_api.json")).mkString) + parser.parseArgument(args) + val master = parser.get("master") + + val hdfsuri = parser.get("namenode") + log.info(s"namenode is $hdfsuri") + + val targetPath = parser.get("targetPath") + log.info(s"targetPath is $targetPath") + + val dataciteDump = parser.get("dataciteDumpPath") + log.info(s"dataciteDump is $dataciteDump") + + val hdfsTargetPath =new Path(targetPath) + log.info(s"hdfsTargetPath is $hdfsTargetPath") + + val spark: SparkSession = SparkSession.builder() + .appName(ImportDatacite.getClass.getSimpleName) + .master(master) + .getOrCreate() + + // ====== Init HDFS File System Object + val conf = new Configuration + // Set FileSystem URI + conf.set("fs.defaultFS", hdfsuri) + + // Because of Maven + conf.set("fs.hdfs.impl", classOf[DistributedFileSystem].getName) + conf.set("fs.file.impl", classOf[LocalFileSystem].getName) + val sc:SparkContext = spark.sparkContext + sc.setLogLevel("ERROR") + + import spark.implicits._ + + + val dataciteAggregator: Aggregator[DataciteType, DataciteType, DataciteType] = new Aggregator[DataciteType, DataciteType, DataciteType] with Serializable { + + override def zero: DataciteType = null + + override def reduce(a: DataciteType, b: DataciteType): DataciteType = { + if (b == null) + return a + if (a == null) + return b + if(a.timestamp >b.timestamp) { + return a + } + b + } + + override def merge(a: DataciteType, b: DataciteType): DataciteType = { + reduce(a,b) + } + + override def bufferEncoder: Encoder[DataciteType] = implicitly[Encoder[DataciteType]] + + override def outputEncoder: Encoder[DataciteType] = implicitly[Encoder[DataciteType]] + + override def finish(reduction: DataciteType): DataciteType = reduction + } + + val dump:Dataset[DataciteType] = spark.read.load(dataciteDump).as[DataciteType] + val ts = dump.select(max("timestamp")).first().getLong(0) + + log.info(s"last Timestamp is $ts") + + val cnt = writeSequenceFile(hdfsTargetPath, ts, conf) + + log.info(s"Imported from Datacite API $cnt documents") + + if (cnt > 0) { + + val inputRdd:RDD[DataciteType] = sc.sequenceFile(targetPath, classOf[Int], classOf[Text]) + .map(s => s._2.toString) + .map(s => convertAPIStringToDataciteItem(s)) + spark.createDataset(inputRdd).write.mode(SaveMode.Overwrite).save(s"${targetPath}_dataset") + + val ds:Dataset[DataciteType] = spark.read.load(s"${targetPath}_dataset").as[DataciteType] + + dump + .union(ds) + .groupByKey(_.doi) + .agg(dataciteAggregator.toColumn) + .map(s=>s._2) + .repartition(4000) + .write.mode(SaveMode.Overwrite).save(s"${dataciteDump}_updated") + + val fs = FileSystem.get(sc.hadoopConfiguration) + fs.delete(new Path(s"$dataciteDump"), true) + fs.rename(new Path(s"${dataciteDump}_updated"),new Path(s"$dataciteDump")) + } + } + + private def writeSequenceFile(hdfsTargetPath: Path, timestamp: Long, conf: Configuration):Long = { + val client = new DataciteAPIImporter(timestamp*1000, 1000) + var i = 0 + try { + val writer = SequenceFile.createWriter(conf, SequenceFile.Writer.file(hdfsTargetPath), SequenceFile.Writer.keyClass(classOf[IntWritable]), SequenceFile.Writer.valueClass(classOf[Text])) + try { + + var start: Long = System.currentTimeMillis + var end: Long = 0 + val key: IntWritable = new IntWritable(i) + val value: Text = new Text + while ( { + client.hasNext + }) { + key.set({ + i += 1; + i - 1 + }) + value.set(client.next()) + writer.append(key, value) + writer.hflush() + if (i % 1000 == 0) { + end = System.currentTimeMillis + val time = (end - start) / 1000.0F + println(s"Imported $i in $time seconds") + start = System.currentTimeMillis + } + } + } finally if (writer != null) writer.close() + } + i + } +} \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/datacite_filter b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/datacite_filter new file mode 100644 index 000000000..ad80d6998 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/datacite_filter @@ -0,0 +1,28 @@ +TUBYDI - Assistir Filmes e Series Online Grátis +123Movies +WATCH FULL MOVIE +Movierulz +Full Movie Online +MOVIé WatcH +The King of Staten Island 2020 Online For Free +Watch Train to Busan 2 2020 online for free +Sixth Sense Movie Novelization +Film Complet streaming vf gratuit en ligne +watch now free +LIVE stream watch +LIVE stream UFC +RBC Heritage live stream +MLBStreams Free +NFL Live Stream +Live Stream Free +Royal Ascot 2020 Live Stream +TV Shows Full Episodes Official +FuboTV +Gomovies +Online Free Trial Access +123watch +DÜŞÜK HAPI +Bebek Düşürme Yöntemleri +WHATSAP İLETİŞİM +Cytotec +düşük hapı \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/exportDataset_parameters.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/exportDataset_parameters.json new file mode 100644 index 000000000..63e080337 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/exportDataset_parameters.json @@ -0,0 +1,21 @@ +[ + { + "paramName": "s", + "paramLongName": "sourcePath", + "paramDescription": "the source mdstore path", + "paramRequired": true + }, + + { + "paramName": "t", + "paramLongName": "targetPath", + "paramDescription": "the target mdstore path", + "paramRequired": true + }, + { + "paramName": "m", + "paramLongName": "master", + "paramDescription": "the master name", + "paramRequired": true + } +] \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/generate_dataset_params.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/generate_dataset_params.json new file mode 100644 index 000000000..34fa3ed99 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/generate_dataset_params.json @@ -0,0 +1,33 @@ +[ + { + "paramName": "s", + "paramLongName": "sourcePath", + "paramDescription": "the source mdstore path", + "paramRequired": true + }, + + { + "paramName": "t", + "paramLongName": "targetPath", + "paramDescription": "the target mdstore path", + "paramRequired": true + }, + { + "paramName": "tr", + "paramLongName": "transformationRule", + "paramDescription": "the transformation Rule", + "paramRequired": true + }, + { + "paramName": "m", + "paramLongName": "master", + "paramDescription": "the master name", + "paramRequired": true + }, + { + "paramName": "i", + "paramLongName": "isLookupUrl", + "paramDescription": "the isLookup URL", + "paramRequired": true + } +] \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/hostedBy_map.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/hostedBy_map.json new file mode 100644 index 000000000..d014dab5a --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/hostedBy_map.json @@ -0,0 +1,1032 @@ +{ + "SND.QOG": { + "openaire_id": "re3data_____::r3d100012231", + "datacite_name": "Quality of Government Institute", + "official_name": "Quality of Government Institute's Data", + "similarity": 0.8985507246376812 + }, + "GESIS.CESSDA": { + "openaire_id": "re3data_____::r3d100010202", + "datacite_name": "CESSDA ERIC", + "official_name": "CESSDA ERIC" + }, + "BL.CRAN": { + "openaire_id": "re3data_____::r3d100012068", + "datacite_name": "Cranfield University", + "official_name": "Cranfield Online Research Data" + }, + "SUL.OPENNEURO": { + "openaire_id": "re3data_____::r3d100010924", + "datacite_name": "OpenNeuro", + "official_name": "OpenNeuro" + }, + "UNAVCO.UNAVCO": { + "openaire_id": "re3data_____::r3d100010872", + "datacite_name": "UNAVCO", + "official_name": "UNAVCO" + }, + "SUL.SDR": { + "openaire_id": "re3data_____::r3d100010710", + "datacite_name": "Stanford Digital Repository", + "official_name": "Stanford Digital Repository" + }, + "DK.ICES": { + "openaire_id": "re3data_____::r3d100011288", + "datacite_name": "International Council for the Exploration of the Sea (ICES)", + "official_name": "International Council for the Exploration of the Sea datasets", + "similarity": 0.8833333333333333 + }, + "CISTI.DFOSCIMR": { + "openaire_id": "re3data_____::r3d100012039", + "datacite_name": "Bedford Institute of Oceanography - Fisheries and Oceans Canada - Ocean Data and Information Section", + "official_name": "Bedford Institute of Oceanography - Oceanographic Databases" + }, + "CSIC.DIGITAL": { + "openaire_id": "re3data_____::r3d100011076", + "datacite_name": "Digital CSIC", + "official_name": "DIGITAL.CSIC" + }, + "TIB.PANGAEA": { + "openaire_id": "re3data_____::r3d100010134", + "datacite_name": "PANGAEA", + "official_name": "PANGAEA" + }, + "PSU.DATACOM": { + "openaire_id": "re3data_____::r3d100010477", + "datacite_name": "Data Commons", + "official_name": "ANU Data Commons", + "similarity": 0.8571428571428571 + }, + "ANDS.CENTRE72": { + "openaire_id": "re3data_____::r3d100010451", + "datacite_name": "PARADISEC", + "official_name": "Pacific and Regional Archive for Digital Sources in Endangered Cultures" + }, + "BL.OXDB": { + "openaire_id": "re3data_____::r3d100011653", + "datacite_name": "Oxford University Library Service Databank", + "official_name": "DataBank, Bodleian Libraries, University of Oxford" + }, + "BL.STANDREW": { + "openaire_id": "re3data_____::r3d100012411", + "datacite_name": "University of St Andrews", + "official_name": "St Andrews Research portal - Research Data" + }, + "TIB.BAFG": { + "openaire_id": "re3data_____::r3d100011664", + "datacite_name": "Bundesanstalt f\u00fcr Gew\u00e4sserkunde", + "official_name": "Geoportal der BFG" + }, + "CRUI.UNIBO": { + "openaire_id": "re3data_____::r3d100012604", + "datacite_name": "Universit\u00e0 degli Studi di Bologna", + "official_name": "AMS Acta" + }, + "GDCC.ODUM-LIBRARY": { + "openaire_id": "re3data_____::r3d100000005", + "datacite_name": "UNC Libraries", + "official_name": "UNC Dataverse" + }, + "RG.RG": { + "openaire_id": "re3data_____::r3d100012227", + "datacite_name": "ResearchGate", + "official_name": "ResearchGate" + }, + "TIB.EUMETSAT": { + "openaire_id": "re3data_____::r3d100010232", + "datacite_name": "EUMETSAT", + "official_name": "Eumetsat" + }, + "SND.SMHI": { + "openaire_id": "re3data_____::r3d100011776", + "datacite_name": "Swedish Meteorological and Hydrological Institute open data", + "official_name": "Swedish Meteorological and Hydrological Institute open data" + }, + "NOAA.NCEI": { + "openaire_id": "re3data_____::r3d100011801", + "datacite_name": "National Oceanic and Atmospheric Administration (NOAA) National Centers for Environmental Information (NCEI)", + "official_name": "NCEI" + }, + "TIB.WDCC": { + "openaire_id": "re3data_____::r3d100010299", + "datacite_name": "World Data Center for Climate", + "official_name": "World Data Center for Climate" + }, + "CNGB.GIGADB": { + "openaire_id": "re3data_____::r3d100010478", + "datacite_name": "GigaDB", + "official_name": "GigaDB" + }, + "DELFT.VLIZ": { + "openaire_id": "re3data_____::r3d100010661", + "datacite_name": "Vlaams Instituut voor de Zee", + "official_name": "Flanders Marine Institute" + }, + "NUS.SB": { + "openaire_id": "re3data_____::r3d100012564", + "datacite_name": "National University of Singapore", + "official_name": "ScholarBank@NUS" + }, + "EDI.EDI": { + "openaire_id": "re3data_____::r3d100010272", + "datacite_name": "Environmental Data Initiative", + "official_name": "Environmental Data Initiative Repository" + }, + "INIST.ADISP": { + "openaire_id": "re3data_____::r3d100010494", + "datacite_name": "Quetelet PROGEDO Diffusion", + "official_name": "Quetelet PROGEDO Diffusion" + }, + "GESIS.SHARE": { + "openaire_id": "re3data_____::r3d100010430", + "datacite_name": "SHARE - ERIC", + "official_name": "Survey of Health, Ageing and Retirement in Europe" + }, + "ANDS.CENTRE-1": { + "openaire_id": "re3data_____::r3d100010864", + "datacite_name": "Griffith University", + "official_name": "Griffith University Research Data Repository" + }, + "BL.READING": { + "openaire_id": "re3data_____::r3d100012064", + "datacite_name": "University of Reading", + "official_name": "University of Reading Research Data Archive" + }, + "CORNELL.CISER": { + "openaire_id": "re3data_____::r3d100011056", + "datacite_name": "CISER Data Archive", + "official_name": "CISER Data Archive" + }, + "DRYAD.DRYAD": { + "openaire_id": "re3data_____::r3d100000044", + "datacite_name": "DRYAD", + "official_name": "DRYAD" + }, + "CDL.PISCO": { + "openaire_id": "re3data_____::r3d100010947", + "datacite_name": "Partnership for Interdisciplinary Studies of Coastal Oceans (PISCO)", + "official_name": "Partnership for Interdisciplinary Studies of Coastal Oceans" + }, + "IEEE.DATAPORT": { + "openaire_id": "re3data_____::r3d100012569", + "datacite_name": "IEEE DataPort", + "official_name": "IEEE DataPort" + }, + "DELFT.MAASTRO": { + "openaire_id": "re3data_____::r3d100011086", + "datacite_name": "MAASTRO Clinic", + "official_name": "CancerData.org" + }, + "USGS.PROD": { + "openaire_id": "re3data_____::r3d100010054", + "datacite_name": "USGS DOI Tool Production Environment", + "official_name": "U.S. Geological Survey" + }, + "GDCC.ODUM-DV": { + "openaire_id": "re3data_____::r3d100000005", + "datacite_name": "Odum Institute Dataverse", + "official_name": "UNC Dataverse" + }, + "CDL.SDSCSG": { + "openaire_id": "re3data_____::r3d100011690", + "datacite_name": "UCSD Signaling Gateway", + "official_name": "UCSD Signaling gateway" + }, + "ORBIS.NKN": { + "openaire_id": "re3data_____::r3d100011587", + "datacite_name": "Northwest Knowledge Network", + "official_name": "Northwest Knowledge Network" + }, + "ANDS.CENTRE63": { + "openaire_id": "re3data_____::r3d100010918", + "datacite_name": "Test: Atlas of Living Australia", + "official_name": "Atlas of Living Australia", + "similarity": 0.8928571428571429 + }, + "SML.TALKBANK": { + "openaire_id": "re3data_____::r3d100010887", + "datacite_name": "TalkBank", + "official_name": "TalkBank" + }, + "CORNELL.LIBRARY": { + "openaire_id": "re3data_____::r3d100012322", + "datacite_name": "Cornell University Library", + "official_name": "eCommons - Cornell's digital repository" + }, + "BL.SOTON": { + "openaire_id": "re3data_____::r3d100011245", + "datacite_name": "University of Southampton", + "official_name": "University of Southampton Institutional Research Repository" + }, + "GESIS.DB-BANK": { + "openaire_id": "re3data_____::r3d100012252", + "datacite_name": "Forschungsdaten- und Servicezentrum der Bundesbank", + "official_name": "Forschungsdaten- und Servicezentrum der Bundesbank" + }, + "ANDS.CENTRE68": { + "openaire_id": "re3data_____::r3d100010918", + "datacite_name": "Atlas of Living Australia", + "official_name": "Atlas of Living Australia" + }, + "ANDS.CENTRE69": { + "openaire_id": "re3data_____::r3d100010914", + "datacite_name": "Australian Ocean Data Network", + "official_name": "Australian Ocean Data Network Portal" + }, + "INIST.CDS": { + "openaire_id": "re3data_____::r3d100010584", + "datacite_name": "Strasbourg Astronomical Data Center", + "official_name": "Strasbourg Astronomical Data Center" + }, + "BL.NHM": { + "openaire_id": "re3data_____::r3d100011675", + "datacite_name": "Natural History Museum, London", + "official_name": "Natural History Museum, Data Portal" + }, + "BL.ADS": { + "openaire_id": "re3data_____::r3d100000006", + "datacite_name": "Archaeology Data Service", + "official_name": "Archaeology Data Service" + }, + "GDCC.JHU": { + "openaire_id": "re3data_____::r3d100011836", + "datacite_name": "Johns Hopkins University Library", + "official_name": "Johns Hopkins Data Archive Dataverse Network" + }, + "BL.ED": { + "openaire_id": "re3data_____::r3d100000047", + "datacite_name": "University of Edinburgh", + "official_name": "Edinburgh DataShare" + }, + "BL.EXETER": { + "openaire_id": "re3data_____::r3d100011202", + "datacite_name": "University of Exeter", + "official_name": "Open Research Exeter" + }, + "BL.NCL": { + "openaire_id": "re3data_____::r3d100012408", + "datacite_name": "Newcastle University", + "official_name": "NCL Data" + }, + "BROWN.BDR": { + "openaire_id": "re3data_____::r3d100011654", + "datacite_name": "Brown Digital Repository", + "official_name": "Brown Digital Repository" + }, + "GDCC.SYR-QDR": { + "openaire_id": "re3data_____::r3d100011038", + "datacite_name": "Syracuse University Qualitative Data Repository", + "official_name": "Qualitative Data Repository" + }, + "BL.BRISTOL": { + "openaire_id": "re3data_____::r3d100011099", + "datacite_name": "University of Bristol", + "official_name": "data.bris Research Data Repository" + }, + "DATACITE.DATACITE": { + "openaire_id": "openaire____::datacite", + "datacite_name": "DataCite", + "official_name": "Datacite" + }, + "ESTDOI.KEEL": { + "openaire_id": "re3data_____::r3d100011941", + "datacite_name": "Keeleressursid. The Center of Estonian Language Resources", + "official_name": "Center of Estonian Language Resources" + }, + "BL.ESSEX": { + "openaire_id": "re3data_____::r3d100012405", + "datacite_name": "University of Essex", + "official_name": "Research Data at Essex" + }, + "PURDUE.MDF": { + "openaire_id": "re3data_____::r3d100012080", + "datacite_name": "Univ Chicago Materials Data Facility", + "official_name": "Materials Data Facility" + }, + "DELFT.KNMI": { + "openaire_id": "re3data_____::r3d100011879", + "datacite_name": "KNMI Data Centre", + "official_name": "KNMI Data Centre" + }, + "CUL.CIESIN": { + "openaire_id": "re3data_____::r3d100010207", + "datacite_name": "Center for International Earth Science Information Network", + "official_name": "Center for International Earth Science Information Network" + }, + "WISC.NEOTOMA": { + "openaire_id": "re3data_____::r3d100011761", + "datacite_name": "Neotoma Paleoecological Database", + "official_name": "Neotoma Paleoecology Database", + "similarity": 0.9180327868852459 + }, + "IRIS.IRIS": { + "openaire_id": "re3data_____::r3d100010268", + "datacite_name": "Incorporated Research Institutions for Seismology", + "official_name": "Incorporated Research Institutions for Seismology" + }, + "ANDS.CENTRE50": { + "openaire_id": "re3data_____::r3d100012378", + "datacite_name": "Analysis and Policy Observatory", + "official_name": "Analysis and Policy Observatory" + }, + "FAO.RING": { + "openaire_id": "re3data_____::r3d100012571", + "datacite_name": "CIARD RING", + "official_name": "CIARD Ring" + }, + "CUL.R2R": { + "openaire_id": "re3data_____::r3d100010735", + "datacite_name": "Rolling Deck to Repository", + "official_name": "Rolling Deck to Repository" + }, + "DEMO.GRIIDC": { + "openaire_id": "re3data_____::r3d100011571", + "datacite_name": "Gulf of Mexico Research Initiative Information and Data Cooperative", + "official_name": "Gulf of Mexico Research Initiative Information and Data Cooperative" + }, + "ANDS.CENTRE-6": { + "openaire_id": "re3data_____::r3d100012268", + "datacite_name": "Curtin University", + "official_name": "Curtin University Research Data Collection" + }, + "ANDS.CENTRE-5": { + "openaire_id": "re3data_____::r3d100012013", + "datacite_name": "TERN Central Portal", + "official_name": "TERN Data Discovery portal" + }, + "FIGSHARE.UCT": { + "openaire_id": "re3data_____::r3d100012633", + "datacite_name": "University of Cape Town (UCT)", + "official_name": "ZivaHub" + }, + "BIBSYS.UIT-ORD": { + "openaire_id": "re3data_____::r3d100012538", + "datacite_name": "DataverseNO", + "official_name": "DataverseNO" + }, + "CISTI.CADC": { + "openaire_id": "re3data_____::r3d100000016", + "datacite_name": "Canadian Astronomy Data Centre", + "official_name": "The Canadian Astronomy Data Centre", + "similarity": 0.9375 + }, + "BL.CCDC": { + "openaire_id": "re3data_____::r3d100010197", + "datacite_name": "The Cambridge Crystallographic Data Centre", + "official_name": "The Cambridge Structural Database" + }, + "BL.UCLD": { + "openaire_id": "re3data_____::r3d100012417", + "datacite_name": "University College London", + "official_name": "UCL Discovery" + }, + "GESIS.RKI": { + "openaire_id": "re3data_____::r3d100010436", + "datacite_name": "'Health Monitoring' Research Data Centre at the Robert Koch Institute", + "official_name": "'Health Monitoring' Research Data Centre at the Robert Koch Institute" + }, + "BL.DRI": { + "openaire_id": "re3data_____::r3d100011805", + "datacite_name": "Digital Repository of Ireland", + "official_name": "Digital Repository of Ireland" + }, + "TIB.KIT-IMK": { + "openaire_id": "re3data_____::r3d100011956", + "datacite_name": "Institute for Meteorology and Climate Research - Atmospheric Trace Gases and Remote Sensing", + "official_name": "CARIBIC" + }, + "DOINZ.LANDCARE": { + "openaire_id": "re3data_____::r3d100011662", + "datacite_name": "Landcare Research New Zealand Ltd", + "official_name": "Landcare Research Data Repository" + }, + "DEMO.EMORY": { + "openaire_id": "re3data_____::r3d100011559", + "datacite_name": "The Cancer Imaging Archive", + "official_name": "The Cancer Imaging Archive" + }, + "UMN.DRUM": { + "openaire_id": "re3data_____::r3d100011393", + "datacite_name": "Data Repository for the University of Minnesota", + "official_name": "Data Repository for the University of Minnesota" + }, + "CISTI.SFU": { + "openaire_id": "re3data_____::r3d100012512", + "datacite_name": "Simon Fraser University", + "official_name": "SFU Radar" + }, + "GESIS.ICPSR": { + "openaire_id": "re3data_____::r3d100010255", + "datacite_name": "ICPSR", + "official_name": "Inter-university Consortium for Political and Social Research" + }, + "ANDS.CENTRE49": { + "openaire_id": "re3data_____::r3d100012145", + "datacite_name": "The University of Melbourne", + "official_name": "melbourne.figshare.com" + }, + "ZBW.IFO": { + "openaire_id": "re3data_____::r3d100010201", + "datacite_name": "LMU-ifo Economics & Business Data Center", + "official_name": "LMU-ifo Economics & Business Data Center" + }, + "TIB.BEILST": { + "openaire_id": "re3data_____::r3d100012329", + "datacite_name": "Beilstein-Institut zur F\u00f6rderung der Chemischen Wissenschaften", + "official_name": "STRENDA DB" + }, + "ZBW.ZBW-JDA": { + "openaire_id": "re3data_____::r3d100012190", + "datacite_name": "ZBW Journal Data Archive", + "official_name": "ZBW Journal Data Archive" + }, + "BL.UKDA": { + "openaire_id": "re3data_____::r3d100010215", + "datacite_name": "UK Data Archive", + "official_name": "UK Data Archive" + }, + "CERN.INSPIRE": { + "openaire_id": "re3data_____::r3d100011077", + "datacite_name": "inspirehep.net", + "official_name": "Inspire-HEP" + }, + "CISTI.OTNDC": { + "openaire_id": "re3data_____::r3d100012083", + "datacite_name": "Ocean Tracking Network", + "official_name": "Ocean Tracking Network" + }, + "CISTI.CC": { + "openaire_id": "re3data_____::r3d100012646", + "datacite_name": "Compute Canada", + "official_name": "Federated Research Data Repository" + }, + "SND.ICOS": { + "openaire_id": "re3data_____::r3d100012203", + "datacite_name": "ICOS Carbon Portal", + "official_name": "ICOS Carbon Portal" + }, + "BL.MENDELEY": { + "openaire_id": "re3data_____::r3d100011868", + "datacite_name": "Mendeley", + "official_name": "Mendeley Data" + }, + "DELFT.UU": { + "openaire_id": "re3data_____::r3d100011201", + "datacite_name": "Universiteit Utrecht", + "official_name": "DataverseNL" + }, + "GESIS.DSZ-BO": { + "openaire_id": "re3data_____::r3d100010439", + "datacite_name": "Data Service Center for Business and Organizational Data", + "official_name": "Data Service Center for Business and Organizational Data" + }, + "TIB.IPK": { + "openaire_id": "re3data_____::r3d100011647", + "datacite_name": "IPK Gatersleben", + "official_name": "IPK Gatersleben" + }, + "GDCC.HARVARD-DV": { + "openaire_id": "re3data_____::r3d100010051", + "datacite_name": "Harvard IQSS Dataverse", + "official_name": "Harvard Dataverse" + }, + "BL.LEEDS": { + "openaire_id": "re3data_____::r3d100011945", + "datacite_name": "University of Leeds", + "official_name": "Research Data Leeds Repository" + }, + "BL.BRUNEL": { + "openaire_id": "re3data_____::r3d100012140", + "datacite_name": "Brunel University London", + "official_name": "Brunel figshare" + }, + "DEMO.ENVIDAT": { + "openaire_id": "re3data_____::r3d100012587", + "datacite_name": "EnviDat", + "official_name": "EnviDat" + }, + "GDCC.NTU": { + "openaire_id": "re3data_____::r3d100012440", + "datacite_name": "Nanyang Technological University", + "official_name": "DR-NTU (Data)" + }, + "UNM.DATAONE": { + "openaire_id": "re3data_____::r3d100000045", + "datacite_name": "DataONE", + "official_name": "DataONE" + }, + "CSC.NRD": { + "openaire_id": "re3data_____::r3d100012157", + "datacite_name": "Ministry of Culture and Education", + "official_name": "IDA Research Data Storage Service" + }, + "GESIS.DIPF": { + "openaire_id": "re3data_____::r3d100010390", + "datacite_name": "Research Data Centre for Education", + "official_name": "Research Data Centre for Education" + }, + "BL.HALLAM": { + "openaire_id": "re3data_____::r3d100011909", + "datacite_name": "Sheffield Hallam University", + "official_name": "Sheffield Hallam University Research Data Archive" + }, + "BL.LSHTM": { + "openaire_id": "re3data_____::r3d100011800", + "datacite_name": "London School of Hygiene and Tropical Medicine", + "official_name": "LSHTM Data Compass" + }, + "SUBGOE.DARIAH": { + "openaire_id": "re3data_____::r3d100011345", + "datacite_name": "Digital Research Infrastructure for the Arts and Humanities", + "official_name": "DARIAH-DE Repository" + }, + "SND.SU": { + "openaire_id": "re3data_____::r3d100012147", + "datacite_name": "Stockholm University", + "official_name": "Stockholm University repository for data" + }, + "GESIS.INDEPTH": { + "openaire_id": "re3data_____::r3d100011392", + "datacite_name": "INDEPTH Network", + "official_name": "INDEPTH Data Repository" + }, + "TIB.FLOSS": { + "openaire_id": "re3data_____::r3d100010863", + "datacite_name": "FLOSS Project, Syracuse University", + "official_name": "FLOSSmole" + }, + "ETHZ.WGMS": { + "openaire_id": "re3data_____::r3d100010627", + "datacite_name": "World Glacier Monitoring Service", + "official_name": "World Glacier Monitoring Service" + }, + "BL.UEL": { + "openaire_id": "re3data_____::r3d100012414", + "datacite_name": "University of East London", + "official_name": "Data.uel" + }, + "DELFT.DATA4TU": { + "openaire_id": "re3data_____::r3d100010216", + "datacite_name": "4TU.Centre for Research Data", + "official_name": "4TU.Centre for Research Data" + }, + "GESIS.IANUS": { + "openaire_id": "re3data_____::r3d100012361", + "datacite_name": "IANUS - FDZ Arch\u00e4ologie & Altertumswissenschaften", + "official_name": "IANUS Datenportal" + }, + "CDL.UCSDCCA": { + "openaire_id": "re3data_____::r3d100011655", + "datacite_name": "California Coastal Atlas", + "official_name": "California Coastal Atlas" + }, + "VIVA.VT": { + "openaire_id": "re3data_____::r3d100012601", + "datacite_name": "Virginia Tech", + "official_name": "VTechData" + }, + "ANDS.CENTRE39": { + "openaire_id": "re3data_____::r3d100011640", + "datacite_name": "University of the Sunshine Coast", + "official_name": "USC Research Bank research data" + }, + "DEMO.OPENKIM": { + "openaire_id": "re3data_____::r3d100011864", + "datacite_name": "OpenKIM", + "official_name": "OpenKIM" + }, + "INIST.OTELO": { + "openaire_id": "re3data_____::r3d100012505", + "datacite_name": "Observatoire Terre Environnement de Lorraine", + "official_name": "ORDaR" + }, + "INIST.ILL": { + "openaire_id": "re3data_____::r3d100012072", + "datacite_name": "Institut Laue-Langevin", + "official_name": "ILL Data Portal" + }, + "ANDS.CENTRE31": { + "openaire_id": "re3data_____::r3d100012378", + "datacite_name": "Test: Analysis and Policy Observatory", + "official_name": "Analysis and Policy Observatory", + "similarity": 0.9117647058823529 + }, + "ANDS.CENTRE30": { + "openaire_id": "re3data_____::r3d100010917", + "datacite_name": "Test: Geoscience Australia", + "official_name": "Geoscience Australia", + "similarity": 0.8695652173913043 + }, + "BL.SALFORD": { + "openaire_id": "re3data_____::r3d100012144", + "datacite_name": "University of Salford", + "official_name": "University of Salford Data Repository" + }, + "CERN.HEPDATA": { + "openaire_id": "re3data_____::r3d100010081", + "datacite_name": "HEPData.net", + "official_name": "HEPData" + }, + "ETHZ.E-COLL": { + "openaire_id": "re3data_____::r3d100012557", + "datacite_name": "ETH Z\u00fcrich Research Collection", + "official_name": "ETH Z\u00fcrich Research Collection" + }, + "GBIF.GBIF": { + "openaire_id": "re3data_____::r3d100000039", + "datacite_name": "Global Biodiversity Information Facility", + "official_name": "Global Biodiversity Information Facility" + }, + "ORNLDAAC.DAAC": { + "openaire_id": "re3data_____::r3d100000037", + "datacite_name": "Oak Ridge National Laboratory Distributed Active Archive Center", + "official_name": "Oak Ridge National Laboratory Distributed Active Archive Center for Biogeochemical Dynamics" + }, + "KAUST.KAUSTREPO": { + "openaire_id": "re3data_____::r3d100011898", + "datacite_name": "KAUST Research Repository", + "official_name": "UWA Research Repository", + "similarity": 0.875 + }, + "ZBW.ZEW": { + "openaire_id": "re3data_____::r3d100010399", + "datacite_name": "Zentrum f\u00fcr Europ\u00e4ische Wirtschaftsforschung GmbH (ZEW)", + "official_name": "ZEW Forschungsdatenzentrum" + }, + "SML.TDAR": { + "openaire_id": "re3data_____::r3d100010347", + "datacite_name": "Digital Antiquity (TDAR)", + "official_name": "tDAR" + }, + "GESIS.CSDA": { + "openaire_id": "re3data_____::r3d100010484", + "datacite_name": "Czech Social Science Data Archive", + "official_name": "Czech Social Science Data Archive" + }, + "SND.BOLIN": { + "openaire_id": "re3data_____::r3d100011699", + "datacite_name": "Bolin Centre Database", + "official_name": "Bolin Centre Database" + }, + "MLA.HC": { + "openaire_id": "re3data_____::r3d100012309", + "datacite_name": "Humanities Commons", + "official_name": "Humanities Commons" + }, + "CDL.IDASHREP": { + "openaire_id": "re3data_____::r3d100010382", + "datacite_name": "iDASH Repository", + "official_name": "IDS Repository", + "similarity": 0.8666666666666667 + }, + "ZBMED.SNSB": { + "openaire_id": "re3data_____::r3d100011873", + "datacite_name": "Staatliche Naturwissenschaftliche Sammlungen Bayerns", + "official_name": "Staatliche Naturwissenschaftliche Sammlungen Bayerns - datasets", + "similarity": 0.9043478260869565 + }, + "ORBIS.OHSU": { + "openaire_id": "re3data_____::r3d100012244", + "datacite_name": "Oregon Health Sciences University", + "official_name": "OHSU Digital Commons" + }, + "DARTLIB.CRAWDAD": { + "openaire_id": "re3data_____::r3d100010716", + "datacite_name": "CRAWDAD", + "official_name": "CRAWDAD" + }, + "CDL.CCHDO": { + "openaire_id": "re3data_____::r3d100010831", + "datacite_name": "CLIVAR and Carbon Hydrographic Data Office", + "official_name": "Climate Variability and Predictability and Carbon Hydrographic Data Office" + }, + "GESIS.AUSSDA": { + "openaire_id": "re3data_____::r3d100010483", + "datacite_name": "Austrian Social Science Data Archive", + "official_name": "AUSSDA" + }, + "NSIDC.DATACTR": { + "openaire_id": "re3data_____::r3d100010110", + "datacite_name": "National Snow and Ice Data Center", + "official_name": "National Snow and Ice Data Center" + }, + "TIB.RADAR": { + "openaire_id": "re3data_____::r3d100012330", + "datacite_name": "FIZ Karlsruhe \u2013 Leibniz-Institut f\u00fcr Informationsinfrastruktur", + "official_name": "RADAR" + }, + "KIM.OPENKIM": { + "openaire_id": "re3data_____::r3d100011864", + "datacite_name": "Open Knowledgebase of Interatomic Models (OpenKIM)", + "official_name": "OpenKIM" + }, + "BL.LBORO": { + "openaire_id": "re3data_____::r3d100012143", + "datacite_name": "Loughborough University", + "official_name": "Loughborough Data Repository" + }, + "GESIS.ZPID": { + "openaire_id": "re3data_____::r3d100010328", + "datacite_name": "GESIS.ZPID", + "official_name": "PsychData" + }, + "SML.TCIA": { + "openaire_id": "re3data_____::r3d100011559", + "datacite_name": "The Cancer Imaging Archive", + "official_name": "The Cancer Imaging Archive" + }, + "CDL.IRIS": { + "openaire_id": "re3data_____::r3d100010268", + "datacite_name": "Incorporated Research Institutions for Seismology", + "official_name": "Incorporated Research Institutions for Seismology" + }, + "BIBSYS.NMDC": { + "openaire_id": "re3data_____::r3d100012291", + "datacite_name": "Norwegian Marine Data Centre", + "official_name": "Norwegian Polar Data Centre", + "similarity": 0.8727272727272727 + }, + "ANDS.CENTRE25": { + "openaire_id": "re3data_____::r3d100010917", + "datacite_name": "Geoscience Australia", + "official_name": "Geoscience Australia" + }, + "BL.UCLAN": { + "openaire_id": "re3data_____::r3d100012019", + "datacite_name": "University of Central Lancashire", + "official_name": "UCLanData" + }, + "ANDS.CENTRE23": { + "openaire_id": "re3data_____::r3d100011898", + "datacite_name": "The University of Western Australia", + "official_name": "UWA Research Repository" + }, + "CISTI.WOUDC": { + "openaire_id": "re3data_____::r3d100010367", + "datacite_name": "World Ozone and Ultraviolet Radiation Data Centre", + "official_name": "World Ozone and Ultraviolet Radiation Data Centre" + }, + "FIGSHARE.ARS": { + "openaire_id": "re3data_____::r3d10001066", + "datacite_name": "figshare Academic Research System", + "official_name": "figshare" + }, + "ILLINOIS.DATABANK": { + "openaire_id": "re3data_____::r3d100012001", + "datacite_name": "Illinois Data Bank", + "official_name": "Illinois Data Bank" + }, + "BL.ECMWF": { + "openaire_id": "re3data_____::r3d100011726", + "datacite_name": "European Centre for Medium-Range Weather Forecasts", + "official_name": "European Centre for Medium-Range Weather Forecasts" + }, + "CDL.ISSDA": { + "openaire_id": "re3data_____::r3d100010497", + "datacite_name": "Irish Social Science Data Archive (ISSDA)", + "official_name": "Irish Social Science Data Archive" + }, + "CDL.PQR": { + "openaire_id": "re3data_____::r3d100012225", + "datacite_name": "Pitt Quantum Repository", + "official_name": "Pitt Quantum Repository" + }, + "ANDS.CENTRE82": { + "openaire_id": "re3data_____::r3d100010138", + "datacite_name": "Test: Australian Data Archive", + "official_name": "Australian Data Archive", + "similarity": 0.8846153846153846 + }, + "GDCC.HARVARD-SLP": { + "openaire_id": "re3data_____::r3d100011861", + "datacite_name": "National Sleep Research Resource", + "official_name": "National Sleep Research Resource" + }, + "CDL.IMMPORT": { + "openaire_id": "re3data_____::r3d100012529", + "datacite_name": "UCSF ImmPort", + "official_name": "ImmPort" + }, + "GESIS.FID": { + "openaire_id": "re3data_____::r3d100012347", + "datacite_name": "FID f\u00fcr internationale und interdisziplin\u00e4re Rechtsforschung", + "official_name": "\u00b2Dok[\u00a7]" + }, + "OCEAN.OCEAN": { + "openaire_id": "re3data_____::r3d100012369", + "datacite_name": "Code Ocean", + "official_name": "Code Ocean" + }, + "CERN.ZENODO": { + "openaire_id": "re3data_____::r3d100010468", + "datacite_name": "Zenodo", + "official_name": "Zenodo" + }, + "ETHZ.DA-RD": { + "openaire_id": "re3data_____::r3d100011626", + "datacite_name": "ETHZ Data Archive - Research Data", + "official_name": "ETH Data Archive" + }, + "SND.ECDS": { + "openaire_id": "re3data_____::r3d100011000", + "datacite_name": "Environment Climate Data Sweden", + "official_name": "Environment Climate Data Sweden" + }, + "BL.BATH": { + "openaire_id": "re3data_____::r3d100011947", + "datacite_name": "University of Bath", + "official_name": "University of Bath Research Data Archive" + }, + "TIB.LDEO": { + "openaire_id": "re3data_____::r3d100012547", + "datacite_name": "LDEO - Lamont-Doherty Earth Observatory, Columbia University", + "official_name": "Lamont-Doherty Core Repository" + }, + "COS.OSF": { + "openaire_id": "re3data_____::r3d100011137", + "datacite_name": "Open Science Framework", + "official_name": "Open Science Framework" + }, + "ESTDOI.REPO": { + "openaire_id": "re3data_____::r3d100012333", + "datacite_name": "DataDOI", + "official_name": "DataDOI" + }, + "CDL.NSFADC": { + "openaire_id": "re3data_____::r3d100011973", + "datacite_name": "NSF Arctic Data Center", + "official_name": "NSF Arctic Data Center" + }, + "ANDS.CENTRE13": { + "openaire_id": "re3data_____::r3d100010477", + "datacite_name": "The Australian National University", + "official_name": "ANU Data Commons" + }, + "BL.NERC": { + "openaire_id": "re3data_____::r3d100010199", + "datacite_name": "Natural Environment Research Council", + "official_name": "Environmental Information Data Centre" + }, + "SAGEBIO.SYNAPSE": { + "openaire_id": "re3data_____::r3d100011894", + "datacite_name": "Synapse", + "official_name": "Synapse" + }, + "ANDS.CENTRE15": { + "openaire_id": "re3data_____::r3d100000038", + "datacite_name": "Australian Antarctic Division", + "official_name": "Australian Antarctic Data Centre" + }, + "WISC.BMRB": { + "openaire_id": "re3data_____::r3d100010191", + "datacite_name": "Biological Magnetic Resonance Bank", + "official_name": "Biological Magnetic Resonance Data Bank", + "similarity": 0.9315068493150684 + }, + "STSCI.MAST": { + "openaire_id": "re3data_____::r3d100010403", + "datacite_name": "Barbara A. Mikulski Archive for Space Telescopes", + "official_name": "Barbara A. Mikulski Archive for Space Telescopes" + }, + "CDL.NSIDC": { + "openaire_id": "re3data_____::r3d100010110", + "datacite_name": "National Snow and Ice Data Center", + "official_name": "National Snow and Ice Data Center" + }, + "BL.STRATH": { + "openaire_id": "re3data_____::r3d100012412", + "datacite_name": "University of Strathclyde", + "official_name": "University of Strathclyde KnowledgeBase Datasets" + }, + "DEMO.TDAR": { + "openaire_id": "re3data_____::r3d100010347", + "datacite_name": "The Digital Archaeological Record (tDAR)", + "official_name": "tDAR" + }, + "TIND.CALTECH": { + "openaire_id": "re3data_____::r3d100012384", + "datacite_name": "CaltechDATA", + "official_name": "CaltechDATA" + }, + "GESIS.BIBB-FDZ": { + "openaire_id": "re3data_____::r3d100010190", + "datacite_name": "Forschungsdatenzentrum im Bundesinstitut f\u00fcr Berufsbildung", + "official_name": "Forschungsdatenzentrum im Bundesinstitut f\u00fcr Berufsbildung" + }, + "ANDS.CENTRE87": { + "openaire_id": "re3data_____::r3d100010138", + "datacite_name": "Australian Data Archive", + "official_name": "Australian Data Archive" + }, + "GESIS.NEPS": { + "openaire_id": "re3data_____::r3d100010736", + "datacite_name": "Nationales Bildungspanel (National Educational Panel Study, NEPS)", + "official_name": "Nationales Bildungspanel" + }, + "CDL.UCBCRCNS": { + "openaire_id": "re3data_____::r3d100011269", + "datacite_name": "Collaborative Research in Computational Neuroscience (CRCNS)", + "official_name": "Collaborative Research in Computational Neuroscience" + }, + "TIB.UKON": { + "openaire_id": "re3data_____::r3d100010469", + "datacite_name": "Movebank", + "official_name": "Movebank" + }, + "UMN.IPUMS": { + "openaire_id": "re3data_____::r3d100010794", + "datacite_name": "Minnesota Population Center", + "official_name": "Minnesota Population Center" + }, + "TIB.BIKF": { + "openaire_id": "re3data_____::r3d100012379", + "datacite_name": "Senckenberg Data & Metadata Repository", + "official_name": "Senckenberg Data & Metadata Repository" + }, + "TDL.GRIIDC": { + "openaire_id": "re3data_____::r3d100011571", + "datacite_name": "Gulf of Mexico Research Initiative Information and Data Cooperative", + "official_name": "Gulf of Mexico Research Initiative Information and Data Cooperative" + }, + "DELFT.NIBG": { + "openaire_id": "re3data_____::r3d100012167", + "datacite_name": "Sound and Vision", + "official_name": "Sound and Vision" + }, + "BL.SURREY": { + "openaire_id": "re3data_____::r3d100012232", + "datacite_name": "University of Surrey", + "official_name": "Surrey Research Insight" + }, + "OSTI.ORNLNGEE": { + "openaire_id": "re3data_____::r3d100011676", + "datacite_name": "NGEE-Arctic (Next Generation Ecosystems Experiement)", + "official_name": "NGEE Arctic" + }, + "TIB.WDCRSAT": { + "openaire_id": "re3data_____::r3d100010156", + "datacite_name": "World Data Center for Remote Sensing of the Atmosphere", + "official_name": "The World Data Center for Remote Sensing of the Atmosphere", + "similarity": 0.9642857142857143 + }, + "ZBMED.DSMZ": { + "openaire_id": "re3data_____::r3d100010219", + "datacite_name": "DSMZ", + "official_name": "DSMZ" + }, + "DOINZ.NZAU": { + "openaire_id": "re3data_____::r3d100012110", + "datacite_name": "University of Auckland Data Publishing and Discovery Service", + "official_name": "University of Auckland Data Repository" + }, + "INIST.RESIF": { + "openaire_id": "re3data_____::r3d100012222", + "datacite_name": "R\u00e9seau sismologique et g\u00e9od\u00e9sique fran\u00e7ais", + "official_name": "RESIF Seismic Data Portal" + }, + "CDL.NCEAS": { + "openaire_id": "re3data_____::r3d100010093", + "datacite_name": "National Center for Ecological Analysis and Synthesis (NCEAS)", + "official_name": "National Center for Ecological Analysis and Synthesis Data Repository" + }, + "ZBMED.EMP": { + "openaire_id": "re3data_____::r3d100010234", + "datacite_name": "eyeMoviePedia", + "official_name": "eyeMoviePedia" + }, + "ZBMED.BIOFRESH": { + "openaire_id": "re3data_____::r3d100011651", + "datacite_name": "Project BioFresh, Leibniz-Institute of Freshwater Ecology and Inland Fisheries", + "official_name": "Freshwater Biodiversity Data Portal" + }, + "INIST.IFREMER": { + "openaire_id": "re3data_____::r3d100011867", + "datacite_name": "Institut Fran\u00e7ais de Recherche pour l'Exploitation de la Mer", + "official_name": "SEANOE" + }, + "ETHZ.SICAS": { + "openaire_id": "re3data_____::r3d100011560", + "datacite_name": "SICAS", + "official_name": "Sicas Medical Image Repository" + }, + "SND.SND": { + "openaire_id": "re3data_____::r3d100010146", + "datacite_name": "Swedish National Data Service", + "official_name": "Swedish National Data Service" + }, + "DELFT.EASY": { + "openaire_id": "re3data_____::r3d100011201", + "datacite_name": "DANS", + "official_name": "DataverseNL" + }, + "WH.WHOAS": { + "openaire_id": "re3data_____::r3d100010423", + "datacite_name": "Woods Hole Open Access Server", + "official_name": "Woods Hole Open Access Server" + }, + "DATACITE.UCSC": { + "openaire_id": "re3data_____::r3d100010243", + "datacite_name": "UCSC Genome Browser", + "official_name": "UCSC Genome Browser" + } +} \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/import_from_api.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/import_from_api.json new file mode 100644 index 000000000..967e4445a --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/import_from_api.json @@ -0,0 +1,27 @@ +[ + { + "paramName": "t", + "paramLongName": "targetPath", + "paramDescription": "the path of the sequencial file to write", + "paramRequired": true + }, + + { + "paramName": "d", + "paramLongName": "dataciteDumpPath", + "paramDescription": "the path of the Datacite dump", + "paramRequired": true + }, + { + "paramName": "n", + "paramLongName": "namenode", + "paramDescription": "the hive metastore uris", + "paramRequired": true + }, + { + "paramName": "m", + "paramLongName": "master", + "paramDescription": "the master name", + "paramRequired": true + } +] \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/config-default.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/config-default.xml new file mode 100644 index 000000000..2e0ed9aee --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/config-default.xml @@ -0,0 +1,18 @@ + + + jobTracker + yarnRM + + + nameNode + hdfs://nameservice1 + + + oozie.use.system.libpath + true + + + oozie.action.sharelib.for.spark + spark2 + + \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/workflow.xml new file mode 100644 index 000000000..a3caa5e23 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/workflow.xml @@ -0,0 +1,103 @@ + + + + mdstoreInputPath + the path of the input MDStore + + + + mdstoreOutputPath + the path of the cleaned mdstore + + + nativeInputPath + the path of the input MDStore + + + + + + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] + + + + + yarn-cluster + cluster + ImportDatacite + eu.dnetlib.dhp.actionmanager.datacite.ImportDatacite + dhp-aggregation-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + + -t${nativeInputPath} + -d${mdstoreInputPath} + -n${nameNode} + --masteryarn-cluster + + + + + + + + + yarn-cluster + cluster + TransformJob + eu.dnetlib.dhp.actionmanager.datacite.GenerateDataciteDatasetSpark + dhp-aggregation-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.sql.shuffle.partitions=3840 + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + + --sourcePath${mdstoreInputPath} + --targetPath${mdstoreOutputPath} + --isLookupUrl${isLookupUrl} + -tr${isLookupUrl} + --masteryarn-cluster + + + + + + + + + yarn-cluster + cluster + ExportDataset + eu.dnetlib.dhp.actionmanager.datacite.ExportActionSetJobNode + dhp-aggregation-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.sql.shuffle.partitions=3840 + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + + --sourcePath${mdstoreOutputPath} + --targetPath${mdstoreOutputPath}_raw_AS + --masteryarn-cluster + + + + + + + \ No newline at end of file diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala index 683986de2..170dc0dc8 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala @@ -111,12 +111,12 @@ object DoiBoostMappingUtil { result.getInstance().asScala.foreach(i => i.setInstancetype(instanceType.get.getInstancetype)) } result.getInstance().asScala.foreach(i => { - i.setHostedby(getUbknownHostedBy()) + i.setHostedby(getUnknownHostedBy()) }) result } - def getUbknownHostedBy():KeyValue = { + def getUnknownHostedBy():KeyValue = { val hb = new KeyValue hb.setValue("Unknown Repository") hb.setKey(s"10|$OPENAIRE_PREFIX::55045bd2a65019fd8e6741a755395c8c") diff --git a/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala b/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala index 8043236e0..3ec391313 100644 --- a/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala +++ b/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala @@ -224,7 +224,7 @@ object DLIToOAF { if (cleanedPids.isEmpty) return null result.setId(generateId(inputPublication.getId)) - result.setDataInfo(generateDataInfo(invisibile = true)) + result.setDataInfo(generateDataInfo(invisible = true)) if (inputPublication.getCollectedfrom == null || inputPublication.getCollectedfrom.size() == 0 || (inputPublication.getCollectedfrom.size() == 1 && inputPublication.getCollectedfrom.get(0) == null)) return null result.setCollectedfrom(inputPublication.getCollectedfrom.asScala.map(c => collectedFromMap.getOrElse(c.getKey, null)).filter(p => p != null).asJava) From 0f8e2ecce6b8e55942bb56de4f4fdae462f25129 Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Fri, 29 Jan 2021 10:45:07 +0100 Subject: [PATCH 071/445] Merged Datacite transfrom into this branch --- dhp-workflows/dhp-aggregation/pom.xml | 14 ++++-------- .../DataciteToOAFTransformation.scala | 14 +++++++++--- .../datacite/ImportDatacite.scala | 1 + .../datacite/oozie_app/workflow.xml | 22 ++++++++++++++++++- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/pom.xml b/dhp-workflows/dhp-aggregation/pom.xml index 0445e0e1b..b61c3d443 100644 --- a/dhp-workflows/dhp-aggregation/pom.xml +++ b/dhp-workflows/dhp-aggregation/pom.xml @@ -37,7 +37,7 @@ - + @@ -58,15 +58,9 @@ eu.dnetlib.dhp dhp-common ${project.version} - - - com.sun.xml.bind - jaxb-core - - - - + + eu.dnetlib.dhp dhp-schemas ${project.version} @@ -116,4 +110,4 @@ - + \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala index 9418e71da..933f1445f 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala @@ -11,9 +11,10 @@ import org.json4s.JsonAST.{JField, JObject, JString} import org.json4s.jackson.JsonMethods.parse import java.nio.charset.CodingErrorAction +import java.text.SimpleDateFormat import java.time.LocalDate import java.time.format.DateTimeFormatter -import java.util.Locale +import java.util.{Date, Locale} import java.util.regex.Pattern import scala.collection.JavaConverters._ import scala.io.{Codec, Source} @@ -44,6 +45,8 @@ object DataciteToOAFTransformation { codec.onMalformedInput(CodingErrorAction.REPLACE) codec.onUnmappableCharacter(CodingErrorAction.REPLACE) + + private val PID_VOCABULARY = "dnet:pid_types" val COBJ_VOCABULARY = "dnet:publication_resource" val RESULT_VOCABULARY = "dnet:result_typologies" @@ -298,8 +301,13 @@ object DataciteToOAFTransformation { result.setPid(List(pid).asJava) result.setId(OafMapperUtils.createOpenaireId(50, s"datacite____::$doi", true)) result.setOriginalId(List(doi).asJava) - result.setDateofcollection(s"${dateOfCollection}") - result.setDateoftransformation(s"$ts") + + val d = new Date(dateOfCollection*1000) + val ISO8601FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US) + + + result.setDateofcollection(ISO8601FORMAT.format(d)) + result.setDateoftransformation(ISO8601FORMAT.format(ts)) result.setDataInfo(dataInfo) val creators = (json \\ "creators").extractOrElse[List[CreatorType]](List()) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ImportDatacite.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ImportDatacite.scala index 06fcbb518..d5edb674a 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ImportDatacite.scala +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ImportDatacite.scala @@ -108,6 +108,7 @@ object ImportDatacite { val cnt = writeSequenceFile(hdfsTargetPath, ts, conf) + log.info(s"Imported from Datacite API $cnt documents") if (cnt > 0) { diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/workflow.xml index a3caa5e23..047794c9c 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/workflow.xml @@ -13,13 +13,25 @@ nativeInputPath the path of the input MDStore + + - + + + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] + + + ${wf:conf('resumeFrom') eq 'TransformJob'} + ${wf:conf('resumeFrom') eq 'ExportDataset'} + + + + yarn-cluster @@ -69,6 +81,14 @@ -tr${isLookupUrl} --masteryarn-cluster + + + + + + + + From d942d0c77d8ee9fbd9028ee31f1284117be295f1 Mon Sep 17 00:00:00 2001 From: "michele.artini" Date: Fri, 29 Jan 2021 13:16:48 +0100 Subject: [PATCH 072/445] methods toString(), hashCode() and equals() --- .../mdstore/manager/common/model/MDStore.java | 20 ++++++++++++++++++ .../common/model/MDStoreCurrentVersion.java | 19 +++++++++++++++++ .../manager/common/model/MDStoreVersion.java | 20 ++++++++++++++++++ .../manager/common/model/MDStoreWithInfo.java | 21 +++++++++++++++++++ 4 files changed, 80 insertions(+) diff --git a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStore.java b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStore.java index 345500737..db200cd6a 100644 --- a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStore.java +++ b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStore.java @@ -3,6 +3,7 @@ package eu.dnetlib.data.mdstore.manager.common.model; import java.io.Serializable; import java.util.Date; +import java.util.Objects; import java.util.UUID; import javax.persistence.Column; @@ -153,4 +154,23 @@ public class MDStore implements Serializable { return md; } + @Override + public String toString() { + return String + .format("MDStore [id=%s, format=%s, layout=%s, interpretation=%s, datasourceName=%s, datasourceId=%s, apiId=%s, hdfsPath=%s, creationDate=%s]", id, format, layout, interpretation, datasourceName, datasourceId, apiId, hdfsPath, creationDate); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof MDStore)) { return false; } + final MDStore other = (MDStore) obj; + return Objects.equals(id, other.id); + } + } diff --git a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreCurrentVersion.java b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreCurrentVersion.java index f74ab39be..e25e7dc2a 100644 --- a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreCurrentVersion.java +++ b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreCurrentVersion.java @@ -2,6 +2,7 @@ package eu.dnetlib.data.mdstore.manager.common.model; import java.io.Serializable; +import java.util.Objects; import javax.persistence.Column; import javax.persistence.Entity; @@ -48,4 +49,22 @@ public class MDStoreCurrentVersion implements Serializable { public static MDStoreCurrentVersion newInstance(final MDStoreVersion v) { return newInstance(v.getMdstore(), v.getId()); } + + @Override + public String toString() { + return String.format("MDStoreCurrentVersion [mdstore=%s, currentVersion=%s]", mdstore, currentVersion); + } + + @Override + public int hashCode() { + return Objects.hash(currentVersion, mdstore); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof MDStoreCurrentVersion)) { return false; } + final MDStoreCurrentVersion other = (MDStoreCurrentVersion) obj; + return Objects.equals(currentVersion, other.currentVersion) && Objects.equals(mdstore, other.mdstore); + } } diff --git a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreVersion.java b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreVersion.java index 62370c0f5..26c34fcad 100644 --- a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreVersion.java +++ b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreVersion.java @@ -3,6 +3,7 @@ package eu.dnetlib.data.mdstore.manager.common.model; import java.io.Serializable; import java.util.Date; +import java.util.Objects; import javax.persistence.Column; import javax.persistence.Entity; @@ -111,4 +112,23 @@ public class MDStoreVersion implements Serializable { public void setHdfsPath(final String hdfsPath) { this.hdfsPath = hdfsPath; } + + @Override + public String toString() { + return String + .format("MDStoreVersion [id=%s, mdstore=%s, writing=%s, readCount=%s, lastUpdate=%s, size=%s, hdfsPath=%s]", id, mdstore, writing, readCount, lastUpdate, size, hdfsPath); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof MDStoreVersion)) { return false; } + final MDStoreVersion other = (MDStoreVersion) obj; + return Objects.equals(id, other.id); + } } diff --git a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreWithInfo.java b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreWithInfo.java index 72915a9c8..e34e4c000 100644 --- a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreWithInfo.java +++ b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreWithInfo.java @@ -3,6 +3,7 @@ package eu.dnetlib.data.mdstore.manager.common.model; import java.io.Serializable; import java.util.Date; +import java.util.Objects; import javax.persistence.Column; import javax.persistence.Entity; @@ -163,4 +164,24 @@ public class MDStoreWithInfo implements Serializable { public void setHdfsPath(final String hdfsPath) { this.hdfsPath = hdfsPath; } + + @Override + public String toString() { + return String + .format("MDStoreWithInfo [id=%s, format=%s, layout=%s, interpretation=%s, datasourceName=%s, datasourceId=%s, apiId=%s, currentVersion=%s, creationDate=%s, lastUpdate=%s, size=%s, numberOfVersions=%s, hdfsPath=%s]", id, format, layout, interpretation, datasourceName, datasourceId, apiId, currentVersion, creationDate, lastUpdate, size, numberOfVersions, hdfsPath); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { return true; } + if (!(obj instanceof MDStoreWithInfo)) { return false; } + final MDStoreWithInfo other = (MDStoreWithInfo) obj; + return Objects.equals(id, other.id); + } + } From 027618003951bcdc51cbd60b7d09163696795386 Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Fri, 29 Jan 2021 16:42:41 +0100 Subject: [PATCH 073/445] WIP mdstore transaction implemented on hadoop side --- .../mdstore/manager/common/model/MDStore.java | 12 +- .../common/model/MDStoreCurrentVersion.java | 8 +- .../manager/common/model/MDStoreVersion.java | 12 +- .../manager/common/model/MDStoreWithInfo.java | 13 +- .../collector/worker/model/ApiDescriptor.java | 2 +- .../dhp/common/rest/DNetRestClient.java | 54 ++++++ .../mdstore/MDStoreActionNode.java | 164 ++++++++++++++++++ .../GenerateNativeStoreSparkJob.java | 46 ++++- .../collection/plugin/CollectorPlugin.java | 2 +- .../plugin/oai/OaiCollectorPlugin.java | 2 +- .../collection/worker/CollectorWorker.java | 5 +- .../worker/CollectorWorkerApplication.java | 28 ++- .../datacite/oozie_app/config-default.xml | 5 + .../collection_input_parameters.json | 12 +- .../dhp/collection/collector_parameter.json | 28 ++- .../collection/mdstore_action_parameters.json | 45 +++++ .../dhp/collection/oozie_app/workflow.xml | 114 ++++++++++-- .../DnetCollectorWorkerApplicationTests.java | 9 +- 18 files changed, 495 insertions(+), 66 deletions(-) rename dhp-common/src/main/java/eu/dnetlib/{ => dhp}/collector/worker/model/ApiDescriptor.java (93%) create mode 100644 dhp-common/src/main/java/eu/dnetlib/dhp/common/rest/DNetRestClient.java create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java create mode 100644 dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/mdstore_action_parameters.json diff --git a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStore.java b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStore.java index db200cd6a..59fe941ed 100644 --- a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStore.java +++ b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStore.java @@ -157,7 +157,9 @@ public class MDStore implements Serializable { @Override public String toString() { return String - .format("MDStore [id=%s, format=%s, layout=%s, interpretation=%s, datasourceName=%s, datasourceId=%s, apiId=%s, hdfsPath=%s, creationDate=%s]", id, format, layout, interpretation, datasourceName, datasourceId, apiId, hdfsPath, creationDate); + .format( + "MDStore [id=%s, format=%s, layout=%s, interpretation=%s, datasourceName=%s, datasourceId=%s, apiId=%s, hdfsPath=%s, creationDate=%s]", + id, format, layout, interpretation, datasourceName, datasourceId, apiId, hdfsPath, creationDate); } @Override @@ -167,8 +169,12 @@ public class MDStore implements Serializable { @Override public boolean equals(final Object obj) { - if (this == obj) { return true; } - if (!(obj instanceof MDStore)) { return false; } + if (this == obj) { + return true; + } + if (!(obj instanceof MDStore)) { + return false; + } final MDStore other = (MDStore) obj; return Objects.equals(id, other.id); } diff --git a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreCurrentVersion.java b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreCurrentVersion.java index e25e7dc2a..d808e2de7 100644 --- a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreCurrentVersion.java +++ b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreCurrentVersion.java @@ -62,8 +62,12 @@ public class MDStoreCurrentVersion implements Serializable { @Override public boolean equals(final Object obj) { - if (this == obj) { return true; } - if (!(obj instanceof MDStoreCurrentVersion)) { return false; } + if (this == obj) { + return true; + } + if (!(obj instanceof MDStoreCurrentVersion)) { + return false; + } final MDStoreCurrentVersion other = (MDStoreCurrentVersion) obj; return Objects.equals(currentVersion, other.currentVersion) && Objects.equals(mdstore, other.mdstore); } diff --git a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreVersion.java b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreVersion.java index 26c34fcad..38f8f275e 100644 --- a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreVersion.java +++ b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreVersion.java @@ -116,7 +116,9 @@ public class MDStoreVersion implements Serializable { @Override public String toString() { return String - .format("MDStoreVersion [id=%s, mdstore=%s, writing=%s, readCount=%s, lastUpdate=%s, size=%s, hdfsPath=%s]", id, mdstore, writing, readCount, lastUpdate, size, hdfsPath); + .format( + "MDStoreVersion [id=%s, mdstore=%s, writing=%s, readCount=%s, lastUpdate=%s, size=%s, hdfsPath=%s]", id, + mdstore, writing, readCount, lastUpdate, size, hdfsPath); } @Override @@ -126,8 +128,12 @@ public class MDStoreVersion implements Serializable { @Override public boolean equals(final Object obj) { - if (this == obj) { return true; } - if (!(obj instanceof MDStoreVersion)) { return false; } + if (this == obj) { + return true; + } + if (!(obj instanceof MDStoreVersion)) { + return false; + } final MDStoreVersion other = (MDStoreVersion) obj; return Objects.equals(id, other.id); } diff --git a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreWithInfo.java b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreWithInfo.java index e34e4c000..510c65092 100644 --- a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreWithInfo.java +++ b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreWithInfo.java @@ -168,7 +168,10 @@ public class MDStoreWithInfo implements Serializable { @Override public String toString() { return String - .format("MDStoreWithInfo [id=%s, format=%s, layout=%s, interpretation=%s, datasourceName=%s, datasourceId=%s, apiId=%s, currentVersion=%s, creationDate=%s, lastUpdate=%s, size=%s, numberOfVersions=%s, hdfsPath=%s]", id, format, layout, interpretation, datasourceName, datasourceId, apiId, currentVersion, creationDate, lastUpdate, size, numberOfVersions, hdfsPath); + .format( + "MDStoreWithInfo [id=%s, format=%s, layout=%s, interpretation=%s, datasourceName=%s, datasourceId=%s, apiId=%s, currentVersion=%s, creationDate=%s, lastUpdate=%s, size=%s, numberOfVersions=%s, hdfsPath=%s]", + id, format, layout, interpretation, datasourceName, datasourceId, apiId, currentVersion, creationDate, + lastUpdate, size, numberOfVersions, hdfsPath); } @Override @@ -178,8 +181,12 @@ public class MDStoreWithInfo implements Serializable { @Override public boolean equals(final Object obj) { - if (this == obj) { return true; } - if (!(obj instanceof MDStoreWithInfo)) { return false; } + if (this == obj) { + return true; + } + if (!(obj instanceof MDStoreWithInfo)) { + return false; + } final MDStoreWithInfo other = (MDStoreWithInfo) obj; return Objects.equals(id, other.id); } diff --git a/dhp-common/src/main/java/eu/dnetlib/collector/worker/model/ApiDescriptor.java b/dhp-common/src/main/java/eu/dnetlib/dhp/collector/worker/model/ApiDescriptor.java similarity index 93% rename from dhp-common/src/main/java/eu/dnetlib/collector/worker/model/ApiDescriptor.java rename to dhp-common/src/main/java/eu/dnetlib/dhp/collector/worker/model/ApiDescriptor.java index bfd70e8c6..8ba30faeb 100644 --- a/dhp-common/src/main/java/eu/dnetlib/collector/worker/model/ApiDescriptor.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/collector/worker/model/ApiDescriptor.java @@ -1,5 +1,5 @@ -package eu.dnetlib.collector.worker.model; +package eu.dnetlib.dhp.collector.worker.model; import java.util.HashMap; import java.util.Map; diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/common/rest/DNetRestClient.java b/dhp-common/src/main/java/eu/dnetlib/dhp/common/rest/DNetRestClient.java new file mode 100644 index 000000000..014f18606 --- /dev/null +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/common/rest/DNetRestClient.java @@ -0,0 +1,54 @@ + +package eu.dnetlib.dhp.common.rest; + +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class DNetRestClient { + + private static ObjectMapper mapper = new ObjectMapper(); + + public static T doGET(final String url, Class clazz) throws Exception { + final HttpGet httpGet = new HttpGet(url); + return doHTTPRequest(httpGet, clazz); + } + + public static String doGET(final String url) throws Exception { + final HttpGet httpGet = new HttpGet(url); + return doHTTPRequest(httpGet); + } + + public static String doPOST(final String url, V objParam) throws Exception { + final HttpPost httpPost = new HttpPost(url); + + if (objParam != null) { + final StringEntity entity = new StringEntity(mapper.writeValueAsString(objParam)); + httpPost.setEntity(entity); + httpPost.setHeader("Accept", "application/json"); + httpPost.setHeader("Content-type", "application/json"); + } + return doHTTPRequest(httpPost); + } + + public static T doPOST(final String url, V objParam, Class clazz) throws Exception { + return mapper.readValue(doPOST(url, objParam), clazz); + } + + private static String doHTTPRequest(final HttpUriRequest r) throws Exception { + CloseableHttpClient client = HttpClients.createDefault(); + CloseableHttpResponse response = client.execute(r); + return IOUtils.toString(response.getEntity().getContent()); + } + + private static T doHTTPRequest(final HttpUriRequest r, Class clazz) throws Exception { + return mapper.readValue(doHTTPRequest(r), clazz); + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java new file mode 100644 index 000000000..d4824ed0a --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java @@ -0,0 +1,164 @@ + +package eu.dnetlib.dhp.aggregation.mdstore; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.net.URI; +import java.util.Properties; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FSDataInputStream; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.collection.worker.CollectorWorker; +import eu.dnetlib.dhp.common.rest.DNetRestClient; + +public class MDStoreActionNode { + private static final Logger log = LoggerFactory.getLogger(MDStoreActionNode.class); + + enum MDAction { + NEW_VERSION, ROLLBACK, COMMIT, READ_LOCK, READ_UNLOCK + + } + + private static final ObjectMapper mapper = new ObjectMapper(); + + public static String NEW_VERSION_URI = "%s/mdstore/%s/newVersion"; + + public static final String COMMIT_VERSION_URL = "%s/version/%s/commit/%s"; + public static final String ROLLBACK_VERSION_URL = "%s/version/%s/abort"; + + public static final String READ_LOCK_URL = "%s/mdstores/mdstore/%s/startReading"; + public static final String READ_UNLOCK_URL = "%s/mdstores/version/%s/endReading"; + + private static final String MDSTOREVERSIONPARAM = "mdStoreVersion"; + private static final String MDSTOREREADLOCKPARAM = "mdStoreReadLockVersion"; + + public static void main(String[] args) throws Exception { + final ArgumentApplicationParser argumentParser = new ArgumentApplicationParser( + IOUtils + .toString( + CollectorWorker.class + .getResourceAsStream( + "/eu/dnetlib/dhp/collection/mdstore_action_parameters.json"))); + argumentParser.parseArgument(args); + + final MDAction action = MDAction.valueOf(argumentParser.get("action")); + log.info("Curren action is {}", action); + + final String mdStoreManagerURI = argumentParser.get("mdStoreManagerURI"); + log.info("mdStoreManagerURI is {}", mdStoreManagerURI); + + switch (action) { + case NEW_VERSION: { + final String mdStoreID = argumentParser.get("mdStoreID"); + if (StringUtils.isBlank(mdStoreID)) { + throw new IllegalArgumentException("missing or empty argument mdStoreId"); + } + final MDStoreVersion currentVersion = DNetRestClient + .doGET(String.format(NEW_VERSION_URI, mdStoreManagerURI, mdStoreID), MDStoreVersion.class); + populateOOZIEEnv(MDSTOREVERSIONPARAM, mapper.writeValueAsString(currentVersion)); + break; + } + case COMMIT: { + + final String hdfsuri = argumentParser.get("namenode"); + if (StringUtils.isBlank(hdfsuri)) { + throw new IllegalArgumentException("missing or empty argument namenode"); + } + final String mdStoreVersion_params = argumentParser.get("mdStoreVersion"); + final MDStoreVersion mdStoreVersion = mapper.readValue(mdStoreVersion_params, MDStoreVersion.class); + + if (StringUtils.isBlank(mdStoreVersion.getId())) { + throw new IllegalArgumentException( + "invalid MDStoreVersion value current is " + mdStoreVersion_params); + } + + Configuration conf = new Configuration(); + // Set FileSystem URI + conf.set("fs.defaultFS", hdfsuri); + // Because of Maven + conf.set("fs.hdfs.impl", org.apache.hadoop.hdfs.DistributedFileSystem.class.getName()); + conf.set("fs.file.impl", org.apache.hadoop.fs.LocalFileSystem.class.getName()); + + System.setProperty("hadoop.home.dir", "/"); + // Get the filesystem - HDFS + FileSystem fs = FileSystem.get(URI.create(hdfsuri), conf); + String mdStoreSizeParam = argumentParser.get("mdStoreSize"); + + if (StringUtils.isBlank(mdStoreSizeParam)) { + throw new IllegalArgumentException("missing or empty argument mdStoreSize"); + } + Path hdfstoreSizepath = new Path(mdStoreVersion.getHdfsPath() + "/size"); + + FSDataInputStream inputStream = fs.open(hdfstoreSizepath); + + final Long mdStoreSize = Long.parseLong(IOUtils.toString(inputStream)); + + inputStream.close(); + fs.create(hdfstoreSizepath); + + DNetRestClient + .doGET(String.format(COMMIT_VERSION_URL, mdStoreManagerURI, mdStoreVersion.getId(), mdStoreSize)); + break; + } + case ROLLBACK: { + final String mdStoreVersion_params = argumentParser.get("mdStoreVersion"); + final MDStoreVersion mdStoreVersion = mapper.readValue(mdStoreVersion_params, MDStoreVersion.class); + + if (StringUtils.isBlank(mdStoreVersion.getId())) { + throw new IllegalArgumentException( + "invalid MDStoreVersion value current is " + mdStoreVersion_params); + } + DNetRestClient.doGET(String.format(ROLLBACK_VERSION_URL, mdStoreManagerURI, mdStoreVersion.getId())); + break; + } + + case READ_LOCK: { + final String mdStoreID = argumentParser.get("mdStoreID"); + if (StringUtils.isBlank(mdStoreID)) { + throw new IllegalArgumentException("missing or empty argument mdStoreId"); + } + final MDStoreVersion currentVersion = DNetRestClient + .doGET(String.format(READ_LOCK_URL, mdStoreManagerURI, mdStoreID), MDStoreVersion.class); + populateOOZIEEnv(MDSTOREREADLOCKPARAM, mapper.writeValueAsString(currentVersion)); + break; + } + case READ_UNLOCK: { + final String mdStoreVersion_params = argumentParser.get("readMDStoreId"); + final MDStoreVersion mdStoreVersion = mapper.readValue(mdStoreVersion_params, MDStoreVersion.class); + + if (StringUtils.isBlank(mdStoreVersion.getId())) { + throw new IllegalArgumentException( + "invalid MDStoreVersion value current is " + mdStoreVersion_params); + } + DNetRestClient.doGET(String.format(READ_UNLOCK_URL, mdStoreManagerURI, mdStoreVersion.getId())); + break; + } + + default: + throw new IllegalArgumentException("invalid action"); + } + + } + + public static void populateOOZIEEnv(final String paramName, String value) throws Exception { + File file = new File(System.getProperty("oozie.action.output.properties")); + Properties props = new Properties(); + + props.setProperty(paramName, value); + OutputStream os = new FileOutputStream(file); + props.store(os, ""); + os.close(); + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java index c9c29b4ea..b28327a40 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java @@ -3,13 +3,17 @@ package eu.dnetlib.dhp.collection; import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; -import java.io.ByteArrayInputStream; +import java.io.*; import java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.Optional; +import java.util.Properties; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.fs.FSDataOutputStream; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.spark.SparkConf; @@ -19,6 +23,7 @@ import org.apache.spark.api.java.JavaSparkContext; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoder; import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.SaveMode; import org.apache.spark.util.LongAccumulator; import org.dom4j.Document; import org.dom4j.Node; @@ -28,7 +33,11 @@ import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; +import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; +import eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode; import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.collection.worker.CollectorWorkerApplication; +import eu.dnetlib.dhp.common.rest.DNetRestClient; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; import eu.dnetlib.dhp.model.mdstore.Provenance; import eu.dnetlib.message.MessageManager; @@ -36,6 +45,7 @@ import eu.dnetlib.message.MessageManager; public class GenerateNativeStoreSparkJob { private static final Logger log = LoggerFactory.getLogger(GenerateNativeStoreSparkJob.class); + private static final String DATASET_NAME = "/store"; public static void main(String[] args) throws Exception { @@ -50,11 +60,15 @@ public class GenerateNativeStoreSparkJob { final String provenanceArgument = parser.get("provenance"); log.info("Provenance is {}", provenanceArgument); final Provenance provenance = jsonMapper.readValue(provenanceArgument, Provenance.class); + final String dateOfCollectionArgs = parser.get("dateOfCollection"); log.info("dateOfCollection is {}", dateOfCollectionArgs); final long dateOfCollection = new Long(dateOfCollectionArgs); - final String sequenceFileInputPath = parser.get("input"); - log.info("sequenceFileInputPath is {}", dateOfCollectionArgs); + + String mdStoreVersion = parser.get("mdStoreVersion"); + log.info("mdStoreVersion is {}", mdStoreVersion); + + final MDStoreVersion currentVersion = jsonMapper.readValue(mdStoreVersion, MDStoreVersion.class); Boolean isSparkSessionManaged = Optional .ofNullable(parser.get("isSparkSessionManaged")) @@ -70,7 +84,9 @@ public class GenerateNativeStoreSparkJob { final JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); final JavaPairRDD inputRDD = sc - .sequenceFile(sequenceFileInputPath, IntWritable.class, Text.class); + .sequenceFile( + currentVersion.getHdfsPath() + CollectorWorkerApplication.SEQUENTIAL_FILE_NAME, + IntWritable.class, Text.class); final LongAccumulator totalItems = sc.sc().longAccumulator("TotalItems"); final LongAccumulator invalidRecords = sc.sc().longAccumulator("InvalidRecords"); @@ -89,12 +105,26 @@ public class GenerateNativeStoreSparkJob { .distinct(); final Encoder encoder = Encoders.bean(MetadataRecord.class); - final Dataset mdstore = spark.createDataset(nativeStore.rdd(), encoder); - final LongAccumulator mdStoreRecords = sc.sc().longAccumulator("MDStoreRecords"); - mdStoreRecords.add(mdstore.count()); + Dataset mdstore = spark.createDataset(nativeStore.rdd(), encoder); - mdstore.write().format("parquet").save(parser.get("output")); + mdstore + .write() + .mode(SaveMode.Overwrite) + .format("parquet") + .save(currentVersion.getHdfsPath() + DATASET_NAME); + mdstore = spark.read().load(currentVersion.getHdfsPath() + DATASET_NAME).as(encoder); + final Long total = mdstore.count(); + + FileSystem fs = FileSystem.get(spark.sparkContext().hadoopConfiguration()); + + FSDataOutputStream output = fs.create(new Path(currentVersion.getHdfsPath() + "/size")); + + final BufferedOutputStream os = new BufferedOutputStream(output); + + os.write(total.toString().getBytes(StandardCharsets.UTF_8)); + + os.close(); }); } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java index 7146e610e..ba9bd662e 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java @@ -3,8 +3,8 @@ package eu.dnetlib.dhp.collection.plugin; import java.util.stream.Stream; -import eu.dnetlib.collector.worker.model.ApiDescriptor; import eu.dnetlib.dhp.collection.worker.CollectorException; +import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; public interface CollectorPlugin { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java index c4c52271a..a5e261553 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java @@ -13,9 +13,9 @@ import com.google.common.base.Splitter; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; -import eu.dnetlib.collector.worker.model.ApiDescriptor; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; import eu.dnetlib.dhp.collection.worker.CollectorException; +import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; public class OaiCollectorPlugin implements CollectorPlugin { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java index 380db641a..3605bdfd6 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java @@ -14,12 +14,9 @@ import org.apache.hadoop.io.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.databind.ObjectMapper; - -import eu.dnetlib.collector.worker.model.ApiDescriptor; -import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginFactory; +import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; public class CollectorWorker { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java index 5e8d0f9c2..29ae98c5b 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java @@ -1,15 +1,22 @@ package eu.dnetlib.dhp.collection.worker; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.Properties; + import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; -import eu.dnetlib.collector.worker.model.ApiDescriptor; +import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginFactory; +import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; +import eu.dnetlib.dhp.common.rest.DNetRestClient; /** * DnetCollectortWorkerApplication is the main class responsible to start the Dnet Collection into HDFS. This module @@ -24,6 +31,8 @@ public class CollectorWorkerApplication { private static final CollectorPluginFactory collectorPluginFactory = new CollectorPluginFactory(); + public static String SEQUENTIAL_FILE_NAME = "/sequence_file"; + /** * @param args */ @@ -38,18 +47,23 @@ public class CollectorWorkerApplication { argumentParser.parseArgument(args); final String hdfsuri = argumentParser.get("namenode"); - log.info("hdfsURI is {}", hdfsuri); - final String hdfsPath = argumentParser.get("hdfsPath"); - log.info("hdfsPath is {}" + hdfsPath); + final String apiDescriptor = argumentParser.get("apidescriptor"); - log.info("apiDescriptor is {}" + apiDescriptor); + log.info("apiDescriptor is {}", apiDescriptor); + + final String mdStoreVersion = argumentParser.get("mdStoreVersion"); + log.info("mdStoreVersion is {}", mdStoreVersion); final ObjectMapper jsonMapper = new ObjectMapper(); - final ApiDescriptor api = jsonMapper.readValue(apiDescriptor, ApiDescriptor.class); + final MDStoreVersion currentVersion = jsonMapper.readValue(mdStoreVersion, MDStoreVersion.class); - final CollectorWorker worker = new CollectorWorker(collectorPluginFactory, api, hdfsuri, hdfsPath); + final ApiDescriptor api = jsonMapper.readValue(apiDescriptor, ApiDescriptor.class); + final CollectorWorker worker = new CollectorWorker(collectorPluginFactory, api, hdfsuri, + currentVersion.getHdfsPath() + SEQUENTIAL_FILE_NAME); worker.collect(); + } + } diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/config-default.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/config-default.xml index 2e0ed9aee..dd3c32c62 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/config-default.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/config-default.xml @@ -15,4 +15,9 @@ oozie.action.sharelib.for.spark spark2 + + + oozie.launcher.mapreduce.user.classpath.first + true + \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collection_input_parameters.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collection_input_parameters.json index 7f5113930..c1aa03bcd 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collection_input_parameters.json +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collection_input_parameters.json @@ -30,15 +30,9 @@ "paramRequired": true }, { - "paramName": "i", - "paramLongName": "input", - "paramDescription": "the path of the sequencial file to read", - "paramRequired": true - }, - { - "paramName": "o", - "paramLongName": "output", - "paramDescription": "the path of the result DataFrame on HDFS", + "paramName": "mv", + "paramLongName": "mdStoreVersion", + "paramDescription": "the Metadata Store Version Info", "paramRequired": true }, { diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_parameter.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_parameter.json index 901664e0d..60e9762ff 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_parameter.json +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_parameter.json @@ -1,6 +1,26 @@ [ - {"paramName":"p", "paramLongName":"hdfsPath", "paramDescription": "the path where storing the sequential file", "paramRequired": true}, - {"paramName":"a", "paramLongName":"apidescriptor", "paramDescription": "the JSON encoding of the API Descriptor", "paramRequired": true}, - {"paramName":"n", "paramLongName":"namenode", "paramDescription": "the Name Node URI", "paramRequired": true}, - {"paramName":"w", "paramLongName":"workflowId", "paramDescription": "the identifier of the dnet Workflow", "paramRequired": false} + { + "paramName": "a", + "paramLongName": "apidescriptor", + "paramDescription": "the JSON encoding of the API Descriptor", + "paramRequired": true + }, + { + "paramName": "n", + "paramLongName": "namenode", + "paramDescription": "the Name Node URI", + "paramRequired": true + }, + { + "paramName": "mv", + "paramLongName": "mdStoreVersion", + "paramDescription": "the MDStore Version bean", + "paramRequired": true + }, + { + "paramName": "w", + "paramLongName": "workflowId", + "paramDescription": "the identifier of the dnet Workflow", + "paramRequired": false + } ] \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/mdstore_action_parameters.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/mdstore_action_parameters.json new file mode 100644 index 000000000..57a218a34 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/mdstore_action_parameters.json @@ -0,0 +1,45 @@ +[ + { + "paramName": "a", + "paramLongName": "action", + "paramDescription": "the JSON encoding of the API Descriptor", + "paramRequired": true + }, + { + "paramName": "mu", + "paramLongName": "mdStoreManagerURI", + "paramDescription": "the MDStore Manager URI", + "paramRequired": true + }, + { + "paramName": "mi", + "paramLongName": "mdStoreID", + "paramDescription": "the Metadata Store ID", + "paramRequired": false + }, + { + "paramName": "ms", + "paramLongName": "mdStoreSize", + "paramDescription": "the Metadata Store Size", + "paramRequired": false + }, + { + "paramName": "mv", + "paramLongName": "mdStoreVersion", + "paramDescription": "the Metadata Version Bean", + "paramRequired": false + }, + { + "paramName": "n", + "paramLongName": "namenode", + "paramDescription": "the Name Node URI", + "paramRequired": false + }, + { + "paramName": "rm", + "paramLongName": "readMDStoreId", + "paramDescription": "the ID Locked to Read", + "paramRequired": false + } + +] \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml index 38cd83da7..28abe0965 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml @@ -1,10 +1,5 @@ - - mdStorePath - the path of the native mdstore - - apiDescription A json encoding of the API Description class @@ -16,7 +11,7 @@ identifierPath - An xpath to retrieve the metadata idnentifier for the generation of DNet Identifier + An xpath to retrieve the metadata identifier for the generation of DNet Identifier @@ -33,26 +28,78 @@ workflowId The identifier of the workflow + + + mdStoreID + The identifier of the mdStore + + + + mdStoreManagerURI + The URI of the MDStore Manager + + + + collectionMode + Should be Refresh or Incremental + + ${jobTracker} ${nameNode} - + + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] + + + + ${wf:conf('collectionMode') eq 'REFRESH'} + ${wf:conf('collectionMode') eq 'INCREMENTAL'} + + + + + + + eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode + --actionREAD_LOCK + --mdStoreID${mdStoreID} + --mdStoreManagerURI${mdStoreManagerURI} + + + + + + + + + + + eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode + --actionNEW_VERSION + --mdStoreID${mdStoreID} + --mdStoreManagerURI${mdStoreManagerURI} + + + + + + eu.dnetlib.dhp.collection.worker.CollectorWorkerApplication - --hdfsPath${workingDir}/sequenceFile_${mdstoreVersion} --apidescriptor${apiDescription} --namenode${nameNode} + --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} - + @@ -75,13 +122,56 @@ --dateOfCollection${timestamp} --provenance${dataSourceInfo} --xpath${identifierPath} - --input${workingDir}/sequenceFile - --output${mdStorePath} - -w${workflowId} + --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} + + + + + + + + ${wf:conf('collectionMode') eq 'REFRESH'} + ${wf:conf('collectionMode') eq 'INCREMENTAL'} + + + + + + + eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode + --actionREAD_UNLOCK + --mdStoreManagerURI${mdStoreManagerURI} + --readMDStoreId${wf:actionData('BeginRead')['mdStoreReadLockVersion']} + + + + + + + + + eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode + --actionCOMMIT + --namenode${nameNode} + --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} + --mdStoreManagerURI${mdStoreManagerURI} + + + + eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode + --actionCOMMIT + --mdStoreVersion${wf:actionData('CollectionWorker')['mdStoreVersion']} + --mdStoreManagerURI${mdStoreManagerURI} + + + + + + \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/DnetCollectorWorkerApplicationTests.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/DnetCollectorWorkerApplicationTests.java index fc19f2064..9abfbacac 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/DnetCollectorWorkerApplicationTests.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/DnetCollectorWorkerApplicationTests.java @@ -2,25 +2,18 @@ package eu.dnetlib.dhp.collector.worker; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.Mockito.*; -import java.io.File; import java.nio.file.Path; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import com.fasterxml.jackson.databind.ObjectMapper; -import eu.dnetlib.collector.worker.model.ApiDescriptor; -import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.collection.worker.CollectorWorker; import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginFactory; -import eu.dnetlib.message.Message; -import eu.dnetlib.message.MessageManager; +import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; @Disabled public class DnetCollectorWorkerApplicationTests { From 8ee82576c686fb7f7ff6c840ce91b6e6809d1dc8 Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Fri, 29 Jan 2021 17:02:46 +0100 Subject: [PATCH 074/445] Collection on Refresh WORKS!!! --- .../dhp/aggregation/mdstore/MDStoreActionNode.java | 8 ++------ .../dnetlib/dhp/collection/CollectionJobTest.java | 13 +++++++++++++ .../resources/eu/dnetlib/dhp/collection/input.json | 9 +++++++++ 3 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/input.json diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java index d4824ed0a..6cb0537b2 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java @@ -38,8 +38,8 @@ public class MDStoreActionNode { public static final String COMMIT_VERSION_URL = "%s/version/%s/commit/%s"; public static final String ROLLBACK_VERSION_URL = "%s/version/%s/abort"; - public static final String READ_LOCK_URL = "%s/mdstores/mdstore/%s/startReading"; - public static final String READ_UNLOCK_URL = "%s/mdstores/version/%s/endReading"; + public static final String READ_LOCK_URL = "%s/mdstore/%s/startReading"; + public static final String READ_UNLOCK_URL = "%s/version/%s/endReading"; private static final String MDSTOREVERSIONPARAM = "mdStoreVersion"; private static final String MDSTOREREADLOCKPARAM = "mdStoreReadLockVersion"; @@ -94,11 +94,7 @@ public class MDStoreActionNode { System.setProperty("hadoop.home.dir", "/"); // Get the filesystem - HDFS FileSystem fs = FileSystem.get(URI.create(hdfsuri), conf); - String mdStoreSizeParam = argumentParser.get("mdStoreSize"); - if (StringUtils.isBlank(mdStoreSizeParam)) { - throw new IllegalArgumentException("missing or empty argument mdStoreSize"); - } Path hdfstoreSizepath = new Path(mdStoreVersion.getHdfsPath() + "/size"); FSDataInputStream inputStream = fs.open(hdfstoreSizepath); diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/CollectionJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/CollectionJobTest.java index c3b05f5c9..6f7bb2bc2 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/CollectionJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/CollectionJobTest.java @@ -16,6 +16,8 @@ import org.junit.jupiter.api.io.TempDir; import com.fasterxml.jackson.databind.ObjectMapper; +import eu.dnetlib.data.mdstore.manager.common.model.MDStoreCurrentVersion; +import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; import eu.dnetlib.dhp.model.mdstore.Provenance; import eu.dnetlib.dhp.schema.common.ModelSupport; @@ -37,6 +39,17 @@ public class CollectionJobTest { spark.stop(); } + @Test + public void testJSONSerialization() throws Exception { + final String s = IOUtils.toString(getClass().getResourceAsStream("input.json")); + System.out.println("s = " + s); + final ObjectMapper mapper = new ObjectMapper(); + MDStoreVersion mi = mapper.readValue(s, MDStoreVersion.class); + + assertNotNull(mi); + + } + @Test public void tesCollection(@TempDir Path testDir) throws Exception { final Provenance provenance = new Provenance("pippo", "puppa", "ns_prefix"); diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/input.json b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/input.json new file mode 100644 index 000000000..4ffc33d24 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/input.json @@ -0,0 +1,9 @@ +{ + "id": "md-7557225f-77cc-407d-bdf4-d2fe03131464-1611935085410", + "mdstore": "md-7557225f-77cc-407d-bdf4-d2fe03131464", + "writing": true, + "readCount": 0, + "lastUpdate": null, + "size": 0, + "hdfsPath": "/data/dnet.dev/mdstore/md-7557225f-77cc-407d-bdf4-d2fe03131464/md-7557225f-77cc-407d-bdf4-d2fe03131464-1611935085410" +} \ No newline at end of file From e423634cb6f1a7c301e5af04a9de8246e1e53d0c Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Fri, 29 Jan 2021 17:21:42 +0100 Subject: [PATCH 075/445] RollBack in case of error WORKS!!! --- .../eu/dnetlib/dhp/collection/oozie_app/workflow.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml index 28abe0965..527ec1727 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml @@ -41,7 +41,7 @@ collectionMode - Should be Refresh or Incremental + Should be REFRESH or INCREMENTAL @@ -164,8 +164,8 @@ eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode - --actionCOMMIT - --mdStoreVersion${wf:actionData('CollectionWorker')['mdStoreVersion']} + --actionROLLBACK + --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} --mdStoreManagerURI${mdStoreManagerURI} From b6b835ef49f3977cd43f1e5a1087720015e3a1ee Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Mon, 1 Feb 2021 08:49:42 +0100 Subject: [PATCH 076/445] update transformation Factory to get Transformation Rule by Id and not by Title --- .../dnetlib/dhp/transformation/TransformationFactory.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java index 58292139a..fbaef1d1f 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java @@ -18,7 +18,7 @@ import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; public class TransformationFactory { private static final Logger log = LoggerFactory.getLogger(TransformationFactory.class); - public static final String TRULE_XQUERY = "for $x in collection('/db/DRIVER/TransformationRuleDSResources/TransformationRuleDSResourceType') where $x//TITLE = \"%s\" return $x//CODE/text()"; + public static final String TRULE_XQUERY = "for $x in collection('/db/DRIVER/TransformationRuleDSResources/TransformationRuleDSResourceType') where $x//RESOURCE_IDENTIFIER/@value = \"%s\" return $x//CODE/text()"; public static MapFunction getTransformationPlugin( final Map jobArgument, final AggregationCounter counters, final ISLookUpService isLookupService) @@ -54,15 +54,15 @@ public class TransformationFactory { } } - private static String queryTransformationRuleFromIS(final String transformationRuleName, + private static String queryTransformationRuleFromIS(final String transformationRuleId, final ISLookUpService isLookUpService) throws Exception { - final String query = String.format(TRULE_XQUERY, transformationRuleName); + final String query = String.format(TRULE_XQUERY, transformationRuleId); log.info("asking query to IS: " + query); List result = isLookUpService.quickSearchProfile(query); if (result == null || result.isEmpty()) throw new DnetTransformationException( - "Unable to find transformation rule with name: " + transformationRuleName); + "Unable to find transformation rule with name: " + transformationRuleId); return result.get(0); } From 6ff234d81bd4cdec1c1c1745b24a7c3901e90afb Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Mon, 1 Feb 2021 13:56:05 +0100 Subject: [PATCH 077/445] Implemented a first prototype of incremental harvesting and trasformation using readlock --- dhp-common/pom.xml | 6 ++ .../common/AggregationUtility.java | 28 ++++++ .../GenerateNativeStoreSparkJob.java | 97 +++++++++++++++---- .../transformation/TransformSparkJobNode.java | 39 ++++---- .../transformation/TransformationFactory.java | 6 +- .../collection_input_parameters.json | 6 ++ .../collection/oozie_app/config-default.xml | 4 + .../dhp/collection/oozie_app/workflow.xml | 33 ++++++- .../oozie_app/config-default.xml | 4 + .../dhp/transformation/oozie_app/workflow.xml | 96 +++++++++++++++--- .../transformation_input_parameters.json | 10 +- .../eu/dnetlib/dhp/transform/ext_simple.xsl | 4 +- .../eu/dnetlib/dhp/transform/input.xml | 96 ++++++------------ 13 files changed, 297 insertions(+), 132 deletions(-) create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java diff --git a/dhp-common/pom.xml b/dhp-common/pom.xml index b295bc1f1..6eb2e0358 100644 --- a/dhp-common/pom.xml +++ b/dhp-common/pom.xml @@ -98,6 +98,12 @@ dnet-pace-core + + org.apache.httpcomponents + httpclient + + + eu.dnetlib.dhp dhp-schemas diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java new file mode 100644 index 000000000..1f5ed27cb --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java @@ -0,0 +1,28 @@ + +package eu.dnetlib.dhp.aggregation.common; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.apache.hadoop.fs.FSDataOutputStream; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.spark.sql.SparkSession; + +public class AggregationUtility { + + public static void writeTotalSizeOnHDFS(final SparkSession spark, final Long total, final String path) + throws IOException { + + FileSystem fs = FileSystem.get(spark.sparkContext().hadoopConfiguration()); + + FSDataOutputStream output = fs.create(new Path(path)); + + final BufferedOutputStream os = new BufferedOutputStream(output); + + os.write(total.toString().getBytes(StandardCharsets.UTF_8)); + + os.close(); + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java index b28327a40..466ddcd21 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java @@ -5,9 +5,9 @@ import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; import java.io.*; import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.Objects; import java.util.Optional; -import java.util.Properties; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; @@ -20,10 +20,9 @@ import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaPairRDD; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Encoder; -import org.apache.spark.sql.Encoders; -import org.apache.spark.sql.SaveMode; +import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.sql.*; +import org.apache.spark.sql.expressions.Aggregator; import org.apache.spark.util.LongAccumulator; import org.dom4j.Document; import org.dom4j.Node; @@ -34,19 +33,62 @@ import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; -import eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode; +import eu.dnetlib.dhp.aggregation.common.AggregationUtility; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.collection.worker.CollectorWorkerApplication; -import eu.dnetlib.dhp.common.rest.DNetRestClient; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; import eu.dnetlib.dhp.model.mdstore.Provenance; -import eu.dnetlib.message.MessageManager; +import scala.Tuple2; public class GenerateNativeStoreSparkJob { private static final Logger log = LoggerFactory.getLogger(GenerateNativeStoreSparkJob.class); private static final String DATASET_NAME = "/store"; + public static class MDStoreAggregator extends Aggregator { + + @Override + public MetadataRecord zero() { + return new MetadataRecord(); + } + + @Override + public MetadataRecord reduce(MetadataRecord b, MetadataRecord a) { + + return getLatestRecord(b, a); + } + + private MetadataRecord getLatestRecord(MetadataRecord b, MetadataRecord a) { + if (b == null) + return a; + + if (a == null) + return b; + return (a.getDateOfCollection() > b.getDateOfCollection()) ? a : b; + } + + @Override + public MetadataRecord merge(MetadataRecord b, MetadataRecord a) { + return getLatestRecord(b, a); + } + + @Override + public MetadataRecord finish(MetadataRecord j) { + return j; + } + + @Override + public Encoder bufferEncoder() { + return Encoders.kryo(MetadataRecord.class); + } + + @Override + public Encoder outputEncoder() { + return Encoders.kryo(MetadataRecord.class); + } + + } + public static void main(String[] args) throws Exception { final ArgumentApplicationParser parser = new ArgumentApplicationParser( @@ -70,6 +112,12 @@ public class GenerateNativeStoreSparkJob { final MDStoreVersion currentVersion = jsonMapper.readValue(mdStoreVersion, MDStoreVersion.class); + String readMdStoreVersionParam = parser.get("readMdStoreVersion"); + log.info("readMdStoreVersion is {}", readMdStoreVersionParam); + + final MDStoreVersion readMdStoreVersion = StringUtils.isBlank(readMdStoreVersionParam) ? null + : jsonMapper.readValue(readMdStoreVersionParam, MDStoreVersion.class); + Boolean isSparkSessionManaged = Optional .ofNullable(parser.get("isSparkSessionManaged")) .map(Boolean::valueOf) @@ -77,6 +125,9 @@ public class GenerateNativeStoreSparkJob { log.info("isSparkSessionManaged: {}", isSparkSessionManaged); SparkConf conf = new SparkConf(); + conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer"); + conf.registerKryoClasses(Collections.singleton(MetadataRecord.class).toArray(new Class[] {})); + runWithSparkSession( conf, isSparkSessionManaged, @@ -105,8 +156,27 @@ public class GenerateNativeStoreSparkJob { .distinct(); final Encoder encoder = Encoders.bean(MetadataRecord.class); + Dataset mdstore = spark.createDataset(nativeStore.rdd(), encoder); + if (readMdStoreVersion != null) { + // INCREMENTAL MODE + + Dataset currentMdStoreVersion = spark + .read() + .load(readMdStoreVersion.getHdfsPath() + DATASET_NAME) + .as(encoder); + TypedColumn aggregator = new MDStoreAggregator().toColumn(); + + mdstore = currentMdStoreVersion + .union(mdstore) + .groupByKey( + (MapFunction) MetadataRecord::getId, + Encoders.STRING()) + .agg(aggregator) + .map((MapFunction, MetadataRecord>) Tuple2::_2, encoder); + + } mdstore .write() .mode(SaveMode.Overwrite) @@ -116,17 +186,8 @@ public class GenerateNativeStoreSparkJob { final Long total = mdstore.count(); - FileSystem fs = FileSystem.get(spark.sparkContext().hadoopConfiguration()); - - FSDataOutputStream output = fs.create(new Path(currentVersion.getHdfsPath() + "/size")); - - final BufferedOutputStream os = new BufferedOutputStream(output); - - os.write(total.toString().getBytes(StandardCharsets.UTF_8)); - - os.close(); + AggregationUtility.writeTotalSizeOnHDFS(spark, total, currentVersion.getHdfsPath() + "/size"); }); - } public static MetadataRecord parseRecord( diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java index c6ed5a1e3..b9df902a1 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java @@ -3,14 +3,11 @@ package eu.dnetlib.dhp.transformation; import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; -import java.io.ByteArrayInputStream; -import java.util.HashMap; +import java.io.IOException; import java.util.Map; -import java.util.Objects; import java.util.Optional; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.spark.SparkConf; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.sql.Dataset; @@ -18,25 +15,18 @@ import org.apache.spark.sql.Encoder; import org.apache.spark.sql.Encoders; import org.apache.spark.sql.SparkSession; import org.apache.spark.util.LongAccumulator; -import org.dom4j.Document; -import org.dom4j.DocumentException; -import org.dom4j.Node; -import org.dom4j.io.SAXReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; import eu.dnetlib.dhp.aggregation.common.AggregationCounter; +import eu.dnetlib.dhp.aggregation.common.AggregationUtility; import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; -import eu.dnetlib.dhp.transformation.vocabulary.VocabularyHelper; -import eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction; -import eu.dnetlib.dhp.utils.DHPUtils; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import eu.dnetlib.message.Message; -import eu.dnetlib.message.MessageManager; -import eu.dnetlib.message.MessageType; public class TransformSparkJobNode { @@ -59,10 +49,14 @@ public class TransformSparkJobNode { .orElse(Boolean.TRUE); log.info("isSparkSessionManaged: {}", isSparkSessionManaged); - final String inputPath = parser.get("mdstoreInputPath"); - final String outputPath = parser.get("mdstoreOutputPath"); + final String mdstoreInputVersion = parser.get("mdstoreInputVersion"); + final String mdstoreOutputVersion = parser.get("mdstoreOutputVersion"); // TODO this variable will be used after implementing Messaging with DNet Aggregator + final ObjectMapper jsonMapper = new ObjectMapper(); + final MDStoreVersion nativeMdStoreVersion = jsonMapper.readValue(mdstoreInputVersion, MDStoreVersion.class); + final MDStoreVersion cleanedMdStoreVersion = jsonMapper.readValue(mdstoreOutputVersion, MDStoreVersion.class); + final String isLookupUrl = parser.get("isLookupUrl"); log.info(String.format("isLookupUrl: %s", isLookupUrl)); @@ -72,11 +66,14 @@ public class TransformSparkJobNode { runWithSparkSession( conf, isSparkSessionManaged, - spark -> transformRecords(parser.getObjectMap(), isLookupService, spark, inputPath, outputPath)); + spark -> transformRecords( + parser.getObjectMap(), isLookupService, spark, nativeMdStoreVersion.getHdfsPath(), + cleanedMdStoreVersion.getHdfsPath())); } public static void transformRecords(final Map args, final ISLookUpService isLookUpService, - final SparkSession spark, final String inputPath, final String outputPath) throws DnetTransformationException { + final SparkSession spark, final String inputPath, final String outputPath) + throws DnetTransformationException, IOException { final LongAccumulator totalItems = spark.sparkContext().longAccumulator("TotalItems"); final LongAccumulator errorItems = spark.sparkContext().longAccumulator("errorItems"); @@ -86,11 +83,13 @@ public class TransformSparkJobNode { final Dataset mdstoreInput = spark.read().format("parquet").load(inputPath).as(encoder); final MapFunction XSLTTransformationFunction = TransformationFactory .getTransformationPlugin(args, ct, isLookUpService); - mdstoreInput.map(XSLTTransformationFunction, encoder).write().save(outputPath); + mdstoreInput.map(XSLTTransformationFunction, encoder).write().save(outputPath + "/store"); log.info("Transformed item " + ct.getProcessedItems().count()); log.info("Total item " + ct.getTotalItems().count()); log.info("Transformation Error item " + ct.getErrorItems().count()); + + AggregationUtility.writeTotalSizeOnHDFS(spark, ct.getProcessedItems().count(), outputPath + "/size"); } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java index fbaef1d1f..d1f896964 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java @@ -30,13 +30,13 @@ public class TransformationFactory { log.info("Transformation plugin required " + transformationPlugin); switch (transformationPlugin) { case "XSLT_TRANSFORM": { - final String transformationRuleName = jobArgument.get("transformationRuleTitle"); - if (StringUtils.isBlank(transformationRuleName)) + final String transformationRuleId = jobArgument.get("transformationRuleId"); + if (StringUtils.isBlank(transformationRuleId)) throw new DnetTransformationException("Missing Parameter transformationRule"); final VocabularyGroup vocabularies = VocabularyGroup.loadVocsFromIS(isLookupService); final String transformationRule = queryTransformationRuleFromIS( - transformationRuleName, isLookupService); + transformationRuleId, isLookupService); final long dateOfTransformation = new Long(jobArgument.get("dateOfTransformation")); return new XSLTTransformationFunction(counters, transformationRule, dateOfTransformation, diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collection_input_parameters.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collection_input_parameters.json index c1aa03bcd..987f004bb 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collection_input_parameters.json +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collection_input_parameters.json @@ -35,6 +35,12 @@ "paramDescription": "the Metadata Store Version Info", "paramRequired": true }, + { + "paramName": "rmv", + "paramLongName": "readMdStoreVersion", + "paramDescription": "the Read Lock Metadata Store Version bean", + "paramRequired": false + }, { "paramName": "w", "paramLongName": "workflowId", diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/config-default.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/config-default.xml index 2e0ed9aee..e77dd09c9 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/config-default.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/config-default.xml @@ -15,4 +15,8 @@ oozie.action.sharelib.for.spark spark2 + + oozie.launcher.mapreduce.user.classpath.first + true + \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml index 527ec1727..9c213bee5 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml @@ -51,7 +51,7 @@ - + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] @@ -61,7 +61,7 @@ ${wf:conf('collectionMode') eq 'REFRESH'} ${wf:conf('collectionMode') eq 'INCREMENTAL'} - + @@ -99,7 +99,7 @@ --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} - +
@@ -123,9 +123,10 @@ --provenance${dataSourceInfo} --xpath${identifierPath} --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} + --readMdStoreVersion${wf:actionData('BeginRead')['mdStoreReadLockVersion']} - + @@ -133,7 +134,7 @@ ${wf:conf('collectionMode') eq 'REFRESH'} ${wf:conf('collectionMode') eq 'INCREMENTAL'} - + @@ -161,6 +162,28 @@ + + + ${wf:conf('collectionMode') eq 'REFRESH'} + ${wf:conf('collectionMode') eq 'INCREMENTAL'} + + + + + + + eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode + --actionREAD_UNLOCK + --mdStoreManagerURI${mdStoreManagerURI} + --readMDStoreId${wf:actionData('BeginRead')['mdStoreReadLockVersion']} + + + + + + + + eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/config-default.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/config-default.xml index 2e0ed9aee..e77dd09c9 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/config-default.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/config-default.xml @@ -15,4 +15,8 @@ oozie.action.sharelib.for.spark spark2 + + oozie.launcher.mapreduce.user.classpath.first + true + \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml index b36bc3766..aff87dc79 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml @@ -1,25 +1,25 @@ - mdstoreInputPath - the path of the native MDStore + mdStoreInputId + the identifier of the native MDStore - - mdstoreOutputPath + mdStoreOutputId + the identifier of the cleaned MDStore + + + mdStoreManagerURI the path of the cleaned mdstore - - transformationRuleTitle + transformationRuleId The transformation Rule to apply - transformationPlugin The transformation Plugin - dateOfTransformation The timestamp of the transformation date @@ -28,11 +28,34 @@ - + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] + + + eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode + --actionREAD_LOCK + --mdStoreID${mdStoreInputId} + --mdStoreManagerURI${mdStoreManagerURI} + + + + + + + + eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode + --actionNEW_VERSION + --mdStoreID${mdStoreOutputId} + --mdStoreManagerURI${mdStoreManagerURI} + + + + + + yarn @@ -49,18 +72,63 @@ --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} - --mdstoreInputPath${mdstoreInputPath} - --mdstoreOutputPath${mdstoreOutputPath} + --mdstoreInputVersion${wf:actionData('StartTransaction')['mdStoreVersion']} + --mdstoreOutputVersion${wf:actionData('BeginRead')['mdStoreReadLockVersion']} --dateOfTransformation${dateOfTransformation} --transformationPlugin${transformationPlugin} - --transformationRuleTitle${transformationRuleTitle} - - + --transformationRuleId${transformationRuleId} + + + + + + + eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode + --actionREAD_UNLOCK + --mdStoreManagerURI${mdStoreManagerURI} + --readMDStoreId${wf:actionData('BeginRead')['mdStoreReadLockVersion']} + + + + + + + + + eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode + --actionCOMMIT + --namenode${nameNode} + --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} + --mdStoreManagerURI${mdStoreManagerURI} + + + + eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode + --actionREAD_UNLOCK + --mdStoreManagerURI${mdStoreManagerURI} + --readMDStoreId${wf:actionData('BeginRead')['mdStoreReadLockVersion']} + + + + + + + + + eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode + --actionROLLBACK + --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} + --mdStoreManagerURI${mdStoreManagerURI} + + + + + diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/transformation_input_parameters.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/transformation_input_parameters.json index cbd2f25ab..d92698de5 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/transformation_input_parameters.json +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/transformation_input_parameters.json @@ -13,19 +13,19 @@ }, { "paramName": "i", - "paramLongName": "mdstoreInputPath", - "paramDescription": "the path of the sequencial file to read", + "paramLongName": "mdstoreInputVersion", + "paramDescription": "the mdStore Version bean of the Input", "paramRequired": true }, { "paramName": "o", - "paramLongName": "mdstoreOutputPath", - "paramDescription": "the path of the result DataFrame on HDFS", + "paramLongName": "mdstoreOutputVersion", + "paramDescription": "the mdStore Version bean of the Output", "paramRequired": true }, { "paramName": "tr", - "paramLongName": "transformationRuleTitle", + "paramLongName": "transformationRuleId", "paramDescription": "the transformation Rule to apply to the input MDStore", "paramRequired": true }, diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl index 9e5f84c11..becd3a05e 100644 --- a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl @@ -9,7 +9,9 @@ - + + + diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input.xml b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input.xml index 8efb3c487..ebe8e919b 100644 --- a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input.xml +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input.xml @@ -1,68 +1,32 @@ - - - - od______2294::00029b7f0a2a7e090e55b625a9079d83 - oai:pub.uni-bielefeld.de:2578942 - 2018-11-23T15:15:33.974+01:00 - od______2294 - oai:pub.uni-bielefeld.de:2578942 - 2018-07-24T13:01:16Z - conference - ddc:000 - conferenceFtxt - driver - open_access - - - - Mobile recommendation agents making online use of visual attention information at the point of sale - Pfeiffer, Thies - Pfeiffer, Jella - Meißner, Martin - Davis, Fred - Riedl, René - Jan, vom Brocke - Léger, Pierre-Majorique - Randolph, Adriane - Mobile Cognitive Assistance Systems - Information Systems - ddc:000 - We aim to utilize online information about visual attention for developing mobile recommendation agents (RAs) for use at the point of sale. Up to now, most RAs are focussed exclusively at personalization in an e-commerce setting. Very little is known, however, about mobile RAs that offer information and assistance at the point of sale based on individual-level feature based preference models (Murray and Häubl 2009). Current attempts provide information about products at the point of sale by manually scanning barcodes or using RFID (Kowatsch et al. 2011, Heijden 2005), e.g. using specific apps for smartphones. We argue that an online access to the current visual attention of the user offers a much larger potential. Integrating mobile eye tracking into ordinary glasses would yield a direct benefit of applying neuroscience methods in the user’s everyday life. First, learning from consumers’ attentional processes over time and adapting recommendations based on this learning allows us to provide very accurate and relevant recommendations, potentially increasing the perceived usefulness. Second, our proposed system needs little explicit user input (no scanning or navigation on screen) making it easy to use. Thus, instead of learning from click behaviour and past customer ratings, as it is the case in the e-commerce setting, the mobile RA learns from eye movements by participating online in every day decision processes. We argue that mobile RAs should be built based on current research in human judgment and decision making (Murray et al. 2010). In our project, we therefore follow a two-step approach: In the empirical basic research stream, we aim to understand the user’s interaction with the product shelf: the actions and patterns of user’s behaviour (eye movements, gestures, approaching a product closer) and their correspondence to the user’s informational needs. In the empirical system development stream, we create prototypes of mobile RAs and test experimentally the factors that influence the user’s adoption. For example, we suggest that a user’s involvement in the process, such as a need for exact nutritional information or for assistance (e.g., reading support for elderly) will influence the user’s intention to use such as system. The experiments are conducted both in our immersive virtual reality supermarket presented in a CAVE, where we can also easily display information to the user and track the eye movement in great accuracy, as well as in real-world supermarkets (see Figure 1), so that the findings can be better generalized to natural decision situations (Gidlöf et al. 2013). In a first pilot study with five randomly chosen participants in a supermarket, we evaluated which sort of mobile RAs consumers favour in order to get a first impression of the user’s acceptance of the technology. Figure 1 shows an excerpt of one consumer’s eye movements during a decision process. First results show long eye cascades and short fixations on many products in situations where users are uncertain and in need for support. Furthermore, we find a surprising acceptance of the technology itself throughout all ages (23 – 61 years). At the same time, consumers express serious fear of being manipulated by such a technology. For that reason, they strongly prefer the information to be provided by trusted third party or shared with family members and friends (see also Murray and Häubl 2009). Our pilot will be followed by a larger field experiment in March in order to learn more about factors that influence the user’s acceptance as well as the eye movement patterns that reflect typical phases of decision processes and indicate the need for support by a RA. - 2013 - info:eu-repo/semantics/conferenceObject - doc-type:conferenceObject - text - https://pub.uni-bielefeld.de/record/2578942 - https://pub.uni-bielefeld.de/download/2578942/2602478 - Pfeiffer T, Pfeiffer J, Meißner M. Mobile recommendation agents making online use of visual attention information at the point of sale. In: Davis F, Riedl R, Jan vom B, Léger P-M, Randolph A, eds. Proceedings of the Gmunden Retreat on NeuroIS 2013. 2013: 3-3. - eng - info:eu-repo/semantics/openAccess + +
+ oai:lib.psnc.pl:278 + 2011-08-25T15:17:13Z + PSNCRepository:PSNCExternalRepository:exhibitions + PSNCRepository:PSNCExternalRepository:Departments + PSNCRepository:PSNCExternalRepository:Departments:NetworkServices + PSNCRepository:PSNCExternalRepository + PSNCRepository:PSNCExternalRepository:publications + PSNCRepository +
+ + + + + + + + + + + + + + + + - - - - http://pub.uni-bielefeld.de/oai - oai:pub.uni-bielefeld.de:2578942 - 2018-07-24T13:01:16Z - http://www.openarchives.org/OAI/2.0/oai_dc/ - - - - false - false - 0.9 - - - - -
+ \ No newline at end of file From bead34d11a889b716a256fb0b763995d2c220f0f Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Mon, 1 Feb 2021 14:58:06 +0100 Subject: [PATCH 078/445] code refactor --- .../GenerateNativeStoreSparkJob.java | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java index 466ddcd21..553a3dc5f 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java @@ -157,8 +157,9 @@ public class GenerateNativeStoreSparkJob { final Encoder encoder = Encoders.bean(MetadataRecord.class); - Dataset mdstore = spark.createDataset(nativeStore.rdd(), encoder); + final Dataset mdstore = spark.createDataset(nativeStore.rdd(), encoder); + final String targetPath = currentVersion.getHdfsPath() + DATASET_NAME; if (readMdStoreVersion != null) { // INCREMENTAL MODE @@ -168,28 +169,35 @@ public class GenerateNativeStoreSparkJob { .as(encoder); TypedColumn aggregator = new MDStoreAggregator().toColumn(); - mdstore = currentMdStoreVersion - .union(mdstore) - .groupByKey( - (MapFunction) MetadataRecord::getId, - Encoders.STRING()) - .agg(aggregator) - .map((MapFunction, MetadataRecord>) Tuple2::_2, encoder); + saveDataset( + currentMdStoreVersion + .union(mdstore) + .groupByKey( + (MapFunction) MetadataRecord::getId, + Encoders.STRING()) + .agg(aggregator) + .map((MapFunction, MetadataRecord>) Tuple2::_2, encoder), + targetPath); + } else { + saveDataset(mdstore, targetPath); } - mdstore - .write() - .mode(SaveMode.Overwrite) - .format("parquet") - .save(currentVersion.getHdfsPath() + DATASET_NAME); - mdstore = spark.read().load(currentVersion.getHdfsPath() + DATASET_NAME).as(encoder); - final Long total = mdstore.count(); + final Long total = spark.read().load(targetPath).count(); AggregationUtility.writeTotalSizeOnHDFS(spark, total, currentVersion.getHdfsPath() + "/size"); }); } + private static void saveDataset(final Dataset currentMdStore, final String targetPath) { + currentMdStore + .write() + .mode(SaveMode.Overwrite) + .format("parquet") + .save(targetPath); + + } + public static MetadataRecord parseRecord( final String input, final String xpath, From 8eaa1fd4b411c6757bc75b9f38a155b106e97a29 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 1 Feb 2021 19:29:10 +0100 Subject: [PATCH 079/445] WIP: metadata collection in INCREMENTAL mode and relative test --- .../dhp/model/mdstore/MetadataRecord.java | 16 +- .../common/AggregationUtility.java | 33 ++- .../GenerateNativeStoreSparkJob.java | 259 +++++++++--------- .../worker/CollectorWorkerApplication.java | 10 +- .../GenerateNativeStoreSparkJobTest.java | 169 ++++++++++++ .../eu/dnetlib/dhp/collection/input.json | 9 - .../dhp/collection/mdStoreVersion_1.json | 9 + .../dhp/collection/mdStoreVersion_2.json | 9 + .../eu/dnetlib/dhp/collection/provenance.json | 5 + .../eu/dnetlib/dhp/collection/sequence_file | Bin 0 -> 52308 bytes 10 files changed, 360 insertions(+), 159 deletions(-) create mode 100644 dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJobTest.java delete mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/input.json create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/mdStoreVersion_1.json create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/mdStoreVersion_2.json create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/provenance.json create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/sequence_file diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/model/mdstore/MetadataRecord.java b/dhp-common/src/main/java/eu/dnetlib/dhp/model/mdstore/MetadataRecord.java index ce65e710f..0b59dcce0 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/model/mdstore/MetadataRecord.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/model/mdstore/MetadataRecord.java @@ -26,13 +26,13 @@ public class MetadataRecord implements Serializable { private String body; /** the date when the record has been stored */ - private long dateOfCollection; + private Long dateOfCollection; /** the date when the record has been stored */ - private long dateOfTransformation; + private Long dateOfTransformation; public MetadataRecord() { - this.dateOfCollection = System.currentTimeMillis(); + } public MetadataRecord( @@ -40,7 +40,7 @@ public class MetadataRecord implements Serializable { String encoding, Provenance provenance, String body, - long dateOfCollection) { + Long dateOfCollection) { this.originalId = originalId; this.encoding = encoding; @@ -90,19 +90,19 @@ public class MetadataRecord implements Serializable { this.body = body; } - public long getDateOfCollection() { + public Long getDateOfCollection() { return dateOfCollection; } - public void setDateOfCollection(long dateOfCollection) { + public void setDateOfCollection(Long dateOfCollection) { this.dateOfCollection = dateOfCollection; } - public long getDateOfTransformation() { + public Long getDateOfTransformation() { return dateOfTransformation; } - public void setDateOfTransformation(long dateOfTransformation) { + public void setDateOfTransformation(Long dateOfTransformation) { this.dateOfTransformation = dateOfTransformation; } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java index 1f5ed27cb..eb971c475 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java @@ -8,21 +8,38 @@ import java.nio.charset.StandardCharsets; import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.SaveMode; import org.apache.spark.sql.SparkSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import eu.dnetlib.dhp.collection.GenerateNativeStoreSparkJob; +import eu.dnetlib.dhp.model.mdstore.MetadataRecord; public class AggregationUtility { + private static final Logger log = LoggerFactory.getLogger(AggregationUtility.class); + public static void writeTotalSizeOnHDFS(final SparkSession spark, final Long total, final String path) throws IOException { - FileSystem fs = FileSystem.get(spark.sparkContext().hadoopConfiguration()); + log.info("writing size ({}) info file {}", total, path); + try (FileSystem fs = FileSystem.get(spark.sparkContext().hadoopConfiguration()); + BufferedOutputStream os = new BufferedOutputStream(fs.create(new Path(path)))) { + os.write(total.toString().getBytes(StandardCharsets.UTF_8)); + os.flush(); + } - FSDataOutputStream output = fs.create(new Path(path)); - - final BufferedOutputStream os = new BufferedOutputStream(output); - - os.write(total.toString().getBytes(StandardCharsets.UTF_8)); - - os.close(); } + + public static void saveDataset(final Dataset mdstore, final String targetPath) { + log.info("saving dataset in: {}", targetPath); + mdstore + .write() + .mode(SaveMode.Overwrite) + .format("parquet") + .save(targetPath); + } + } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java index 553a3dc5f..bbed36a9c 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java @@ -1,23 +1,20 @@ package eu.dnetlib.dhp.collection; +import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.*; import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; import java.io.*; import java.nio.charset.StandardCharsets; -import java.util.Collections; +import java.util.List; import java.util.Objects; import java.util.Optional; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.hadoop.fs.FSDataOutputStream; -import org.apache.hadoop.fs.FileSystem; -import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.spark.SparkConf; -import org.apache.spark.api.java.JavaPairRDD; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; import org.apache.spark.api.java.function.MapFunction; @@ -30,31 +27,155 @@ import org.dom4j.io.SAXReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; -import eu.dnetlib.dhp.aggregation.common.AggregationUtility; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.collection.worker.CollectorWorkerApplication; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; import eu.dnetlib.dhp.model.mdstore.Provenance; +import net.sf.saxon.expr.Component; import scala.Tuple2; public class GenerateNativeStoreSparkJob { private static final Logger log = LoggerFactory.getLogger(GenerateNativeStoreSparkJob.class); + + private static final ObjectMapper MAPPER = new ObjectMapper(); + private static final String DATASET_NAME = "/store"; + public static void main(String[] args) throws Exception { + + final ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + GenerateNativeStoreSparkJob.class + .getResourceAsStream( + "/eu/dnetlib/dhp/collection/collection_input_parameters.json"))); + parser.parseArgument(args); + + final String provenanceArgument = parser.get("provenance"); + log.info("Provenance is {}", provenanceArgument); + final Provenance provenance = MAPPER.readValue(provenanceArgument, Provenance.class); + + final String dateOfCollectionArgs = parser.get("dateOfCollection"); + log.info("dateOfCollection is {}", dateOfCollectionArgs); + final Long dateOfCollection = new Long(dateOfCollectionArgs); + + String mdStoreVersion = parser.get("mdStoreVersion"); + log.info("mdStoreVersion is {}", mdStoreVersion); + + final MDStoreVersion currentVersion = MAPPER.readValue(mdStoreVersion, MDStoreVersion.class); + + String readMdStoreVersionParam = parser.get("readMdStoreVersion"); + log.info("readMdStoreVersion is {}", readMdStoreVersionParam); + + final MDStoreVersion readMdStoreVersion = StringUtils.isBlank(readMdStoreVersionParam) ? null + : MAPPER.readValue(readMdStoreVersionParam, MDStoreVersion.class); + + final String xpath = parser.get("xpath"); + log.info("xpath is {}", xpath); + + final String encoding = parser.get("encoding"); + log.info("encoding is {}", encoding); + + Boolean isSparkSessionManaged = Optional + .ofNullable(parser.get("isSparkSessionManaged")) + .map(Boolean::valueOf) + .orElse(Boolean.TRUE); + log.info("isSparkSessionManaged: {}", isSparkSessionManaged); + + SparkConf conf = new SparkConf(); + /* + * conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer"); conf .registerKryoClasses( new + * Class[] { MetadataRecord.class, Provenance.class }); + */ + + runWithSparkSession( + conf, + isSparkSessionManaged, + spark -> createNativeMDStore( + spark, provenance, dateOfCollection, xpath, encoding, currentVersion, readMdStoreVersion)); + } + + private static void createNativeMDStore(SparkSession spark, + Provenance provenance, + Long dateOfCollection, + String xpath, + String encoding, + MDStoreVersion currentVersion, + MDStoreVersion readVersion) throws IOException { + final JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); + + final LongAccumulator totalItems = sc.sc().longAccumulator("TotalItems"); + final LongAccumulator invalidRecords = sc.sc().longAccumulator("InvalidRecords"); + + final String seqFilePath = currentVersion.getHdfsPath() + CollectorWorkerApplication.SEQUENCE_FILE_NAME; + final JavaRDD nativeStore = sc + .sequenceFile(seqFilePath, IntWritable.class, Text.class) + .map( + item -> parseRecord( + item._2().toString(), + xpath, + encoding, + provenance, + dateOfCollection, + totalItems, + invalidRecords)) + .filter(Objects::nonNull) + .distinct(); + + final Encoder encoder = Encoders.bean(MetadataRecord.class); + final Dataset mdstore = spark.createDataset(nativeStore.rdd(), encoder); + + final String targetPath = currentVersion.getHdfsPath() + DATASET_NAME; + + if (readVersion != null) { // INCREMENTAL MODE + log.info("updating {} incrementally with {}", targetPath, readVersion.getHdfsPath()); + Dataset currentMdStoreVersion = spark + .read() + .load(readVersion.getHdfsPath() + DATASET_NAME) + .as(encoder); + TypedColumn aggregator = new MDStoreAggregator().toColumn(); + + final Dataset map = currentMdStoreVersion + .union(mdstore) + .groupByKey( + (MapFunction) MetadataRecord::getId, + Encoders.STRING()) + .agg(aggregator) + .map((MapFunction, MetadataRecord>) Tuple2::_2, encoder); + + map.select("id").takeAsList(100).forEach(s -> log.info(s.toString())); + + saveDataset(map, targetPath); + + } else { + saveDataset(mdstore, targetPath); + } + + final Long total = spark.read().load(targetPath).count(); + log.info("collected {} records for datasource '{}'", total, provenance.getDatasourceName()); + + writeTotalSizeOnHDFS(spark, total, currentVersion.getHdfsPath() + "/size"); + } + public static class MDStoreAggregator extends Aggregator { @Override public MetadataRecord zero() { - return new MetadataRecord(); + return null; } @Override public MetadataRecord reduce(MetadataRecord b, MetadataRecord a) { + return getLatestRecord(b, a); + } + @Override + public MetadataRecord merge(MetadataRecord b, MetadataRecord a) { return getLatestRecord(b, a); } @@ -68,136 +189,22 @@ public class GenerateNativeStoreSparkJob { } @Override - public MetadataRecord merge(MetadataRecord b, MetadataRecord a) { - return getLatestRecord(b, a); - } - - @Override - public MetadataRecord finish(MetadataRecord j) { - return j; + public MetadataRecord finish(MetadataRecord r) { + return r; } @Override public Encoder bufferEncoder() { - return Encoders.kryo(MetadataRecord.class); + return Encoders.bean(MetadataRecord.class); } @Override public Encoder outputEncoder() { - return Encoders.kryo(MetadataRecord.class); + return Encoders.bean(MetadataRecord.class); } } - public static void main(String[] args) throws Exception { - - final ArgumentApplicationParser parser = new ArgumentApplicationParser( - IOUtils - .toString( - GenerateNativeStoreSparkJob.class - .getResourceAsStream( - "/eu/dnetlib/dhp/collection/collection_input_parameters.json"))); - parser.parseArgument(args); - final ObjectMapper jsonMapper = new ObjectMapper(); - final String provenanceArgument = parser.get("provenance"); - log.info("Provenance is {}", provenanceArgument); - final Provenance provenance = jsonMapper.readValue(provenanceArgument, Provenance.class); - - final String dateOfCollectionArgs = parser.get("dateOfCollection"); - log.info("dateOfCollection is {}", dateOfCollectionArgs); - final long dateOfCollection = new Long(dateOfCollectionArgs); - - String mdStoreVersion = parser.get("mdStoreVersion"); - log.info("mdStoreVersion is {}", mdStoreVersion); - - final MDStoreVersion currentVersion = jsonMapper.readValue(mdStoreVersion, MDStoreVersion.class); - - String readMdStoreVersionParam = parser.get("readMdStoreVersion"); - log.info("readMdStoreVersion is {}", readMdStoreVersionParam); - - final MDStoreVersion readMdStoreVersion = StringUtils.isBlank(readMdStoreVersionParam) ? null - : jsonMapper.readValue(readMdStoreVersionParam, MDStoreVersion.class); - - Boolean isSparkSessionManaged = Optional - .ofNullable(parser.get("isSparkSessionManaged")) - .map(Boolean::valueOf) - .orElse(Boolean.TRUE); - log.info("isSparkSessionManaged: {}", isSparkSessionManaged); - - SparkConf conf = new SparkConf(); - conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer"); - conf.registerKryoClasses(Collections.singleton(MetadataRecord.class).toArray(new Class[] {})); - - runWithSparkSession( - conf, - isSparkSessionManaged, - spark -> { - final JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); - - final JavaPairRDD inputRDD = sc - .sequenceFile( - currentVersion.getHdfsPath() + CollectorWorkerApplication.SEQUENTIAL_FILE_NAME, - IntWritable.class, Text.class); - - final LongAccumulator totalItems = sc.sc().longAccumulator("TotalItems"); - final LongAccumulator invalidRecords = sc.sc().longAccumulator("InvalidRecords"); - - final JavaRDD nativeStore = inputRDD - .map( - item -> parseRecord( - item._2().toString(), - parser.get("xpath"), - parser.get("encoding"), - provenance, - dateOfCollection, - totalItems, - invalidRecords)) - .filter(Objects::nonNull) - .distinct(); - - final Encoder encoder = Encoders.bean(MetadataRecord.class); - - final Dataset mdstore = spark.createDataset(nativeStore.rdd(), encoder); - - final String targetPath = currentVersion.getHdfsPath() + DATASET_NAME; - if (readMdStoreVersion != null) { - // INCREMENTAL MODE - - Dataset currentMdStoreVersion = spark - .read() - .load(readMdStoreVersion.getHdfsPath() + DATASET_NAME) - .as(encoder); - TypedColumn aggregator = new MDStoreAggregator().toColumn(); - - saveDataset( - currentMdStoreVersion - .union(mdstore) - .groupByKey( - (MapFunction) MetadataRecord::getId, - Encoders.STRING()) - .agg(aggregator) - .map((MapFunction, MetadataRecord>) Tuple2::_2, encoder), - targetPath); - - } else { - saveDataset(mdstore, targetPath); - } - - final Long total = spark.read().load(targetPath).count(); - - AggregationUtility.writeTotalSizeOnHDFS(spark, total, currentVersion.getHdfsPath() + "/size"); - }); - } - - private static void saveDataset(final Dataset currentMdStore, final String targetPath) { - currentMdStore - .write() - .mode(SaveMode.Overwrite) - .format("parquet") - .save(targetPath); - - } - public static MetadataRecord parseRecord( final String input, final String xpath, @@ -219,7 +226,7 @@ public class GenerateNativeStoreSparkJob { invalidRecords.add(1); return null; } - return new MetadataRecord(originalIdentifier, encoding, provenance, input, dateOfCollection); + return new MetadataRecord(originalIdentifier, encoding, provenance, document.asXML(), dateOfCollection); } catch (Throwable e) { invalidRecords.add(1); return null; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java index 29ae98c5b..e24b9ad1d 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java @@ -1,11 +1,6 @@ package eu.dnetlib.dhp.collection.worker; -import java.io.File; -import java.io.FileOutputStream; -import java.io.OutputStream; -import java.util.Properties; - import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,7 +11,6 @@ import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginFactory; import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; -import eu.dnetlib.dhp.common.rest.DNetRestClient; /** * DnetCollectortWorkerApplication is the main class responsible to start the Dnet Collection into HDFS. This module @@ -31,7 +25,7 @@ public class CollectorWorkerApplication { private static final CollectorPluginFactory collectorPluginFactory = new CollectorPluginFactory(); - public static String SEQUENTIAL_FILE_NAME = "/sequence_file"; + public static String SEQUENCE_FILE_NAME = "/sequence_file"; /** * @param args @@ -61,7 +55,7 @@ public class CollectorWorkerApplication { final ApiDescriptor api = jsonMapper.readValue(apiDescriptor, ApiDescriptor.class); final CollectorWorker worker = new CollectorWorker(collectorPluginFactory, api, hdfsuri, - currentVersion.getHdfsPath() + SEQUENTIAL_FILE_NAME); + currentVersion.getHdfsPath() + SEQUENCE_FILE_NAME); worker.collect(); } diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJobTest.java new file mode 100644 index 000000000..715ad8fa6 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJobTest.java @@ -0,0 +1,169 @@ + +package eu.dnetlib.dhp.collection; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.hadoop.io.IntWritable; +import org.apache.hadoop.io.Text; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaSparkContext; +import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoder; +import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; +import eu.dnetlib.dhp.model.mdstore.MetadataRecord; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class GenerateNativeStoreSparkJobTest { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private static SparkSession spark; + + private static Path workingDir; + + private static Encoder encoder; + + private static final String encoding = "XML"; + private static final String dateOfCollection = System.currentTimeMillis() + ""; + private static final String xpath = "//*[local-name()='header']/*[local-name()='identifier']"; + private static String provenance; + + private static final Logger log = LoggerFactory.getLogger(GenerateNativeStoreSparkJobTest.class); + + @BeforeAll + public static void beforeAll() throws IOException { + provenance = IOUtils.toString(GenerateNativeStoreSparkJobTest.class.getResourceAsStream("provenance.json")); + workingDir = Files.createTempDirectory(GenerateNativeStoreSparkJobTest.class.getSimpleName()); + log.info("using work dir {}", workingDir); + + SparkConf conf = new SparkConf(); + + conf.setAppName(GenerateNativeStoreSparkJobTest.class.getSimpleName()); + + conf.setMaster("local[*]"); + conf.set("spark.driver.host", "localhost"); + conf.set("hive.metastore.local", "true"); + conf.set("spark.ui.enabled", "false"); + conf.set("spark.sql.warehouse.dir", workingDir.toString()); + conf.set("hive.metastore.warehouse.dir", workingDir.resolve("warehouse").toString()); + + encoder = Encoders.bean(MetadataRecord.class); + spark = SparkSession + .builder() + .appName(GenerateNativeStoreSparkJobTest.class.getSimpleName()) + .config(conf) + .getOrCreate(); + } + + @AfterAll + public static void afterAll() throws IOException { + FileUtils.deleteDirectory(workingDir.toFile()); + spark.stop(); + } + + @Test + @Order(1) + public void testGenerateNativeStoreSparkJobRefresh() throws Exception { + + MDStoreVersion mdStoreV1 = prepareVersion("mdStoreVersion_1.json"); + FileUtils.forceMkdir(new File(mdStoreV1.getHdfsPath())); + + IOUtils + .copy( + getClass().getResourceAsStream("sequence_file"), + new FileOutputStream(mdStoreV1.getHdfsPath() + "/sequence_file")); + + GenerateNativeStoreSparkJob + .main( + new String[] { + "-isSparkSessionManaged", Boolean.FALSE.toString(), + "-encoding", encoding, + "-dateOfCollection", dateOfCollection, + "-provenance", provenance, + "-xpath", xpath, + "-mdStoreVersion", OBJECT_MAPPER.writeValueAsString(mdStoreV1), + "-readMdStoreVersion", "", + "-workflowId", "abc" + }); + + verify(mdStoreV1); + } + + @Test + @Order(2) + public void testGenerateNativeStoreSparkJobIncremental() throws Exception { + + MDStoreVersion mdStoreV2 = prepareVersion("mdStoreVersion_2.json"); + FileUtils.forceMkdir(new File(mdStoreV2.getHdfsPath())); + + IOUtils + .copy( + getClass().getResourceAsStream("sequence_file"), + new FileOutputStream(mdStoreV2.getHdfsPath() + "/sequence_file")); + + MDStoreVersion mdStoreV1 = prepareVersion("mdStoreVersion_1.json"); + + GenerateNativeStoreSparkJob + .main( + new String[] { + "-isSparkSessionManaged", Boolean.FALSE.toString(), + "-encoding", encoding, + "-dateOfCollection", dateOfCollection, + "-provenance", provenance, + "-xpath", xpath, + "-mdStoreVersion", OBJECT_MAPPER.writeValueAsString(mdStoreV2), + "-readMdStoreVersion", OBJECT_MAPPER.writeValueAsString(mdStoreV1), + "-workflowId", "abc" + }); + + verify(mdStoreV2); + } + + protected void verify(MDStoreVersion mdStoreVersion) throws IOException { + Assertions.assertTrue(new File(mdStoreVersion.getHdfsPath()).exists()); + + final JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); + long seqFileSize = sc + .sequenceFile(mdStoreVersion.getHdfsPath() + "/sequence_file", IntWritable.class, Text.class) + .count(); + + final Dataset mdstore = spark.read().load(mdStoreVersion.getHdfsPath() + "/store").as(encoder); + long mdStoreSize = mdstore.count(); + + long declaredSize = Long.parseLong(IOUtils.toString(new FileReader(mdStoreVersion.getHdfsPath() + "/size"))); + + Assertions.assertEquals(seqFileSize, declaredSize, "the size must be equal"); + Assertions.assertEquals(seqFileSize, mdStoreSize, "the size must be equal"); + + long uniqueIds = mdstore + .map((MapFunction) MetadataRecord::getId, Encoders.STRING()) + .distinct() + .count(); + + Assertions.assertEquals(seqFileSize, uniqueIds, "the size must be equal"); + } + + private MDStoreVersion prepareVersion(String filename) throws IOException { + MDStoreVersion mdstore = OBJECT_MAPPER + .readValue(IOUtils.toString(getClass().getResource(filename)), MDStoreVersion.class); + mdstore.setHdfsPath(String.format(mdstore.getHdfsPath(), workingDir.toString())); + return mdstore; + } + +} diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/input.json b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/input.json deleted file mode 100644 index 4ffc33d24..000000000 --- a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/input.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "id": "md-7557225f-77cc-407d-bdf4-d2fe03131464-1611935085410", - "mdstore": "md-7557225f-77cc-407d-bdf4-d2fe03131464", - "writing": true, - "readCount": 0, - "lastUpdate": null, - "size": 0, - "hdfsPath": "/data/dnet.dev/mdstore/md-7557225f-77cc-407d-bdf4-d2fe03131464/md-7557225f-77cc-407d-bdf4-d2fe03131464-1611935085410" -} \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/mdStoreVersion_1.json b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/mdStoreVersion_1.json new file mode 100644 index 000000000..8945c3d88 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/mdStoreVersion_1.json @@ -0,0 +1,9 @@ +{ + "id":"md-84e86d00-5771-4ed9-b17f-177ef4b46e42-1612187678801", + "mdstore":"md-84e86d00-5771-4ed9-b17f-177ef4b46e42", + "writing":true, + "readCount":0, + "lastUpdate":null, + "size":0, + "hdfsPath":"%s/mdstore/md-84e86d00-5771-4ed9-b17f-177ef4b46e42/v1" +} \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/mdStoreVersion_2.json b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/mdStoreVersion_2.json new file mode 100644 index 000000000..c3d4617cb --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/mdStoreVersion_2.json @@ -0,0 +1,9 @@ +{ + "id":"md-84e86d00-5771-4ed9-b17f-177ef4b46e42-1612187459108", + "mdstore":"md-84e86d00-5771-4ed9-b17f-177ef4b46e42", + "writing":false, + "readCount":1, + "lastUpdate":1612187563099, + "size":71, + "hdfsPath":"%s/mdstore/md-84e86d00-5771-4ed9-b17f-177ef4b46e42/v2" +} \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/provenance.json b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/provenance.json new file mode 100644 index 000000000..2cf0dab70 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/provenance.json @@ -0,0 +1,5 @@ +{ + "datasourceId":"74912366-d6df-49c1-a1fd-8a52fa98ce5f_UmVwb3NpdG9yeVNlcnZpY2VSZXNvdXJjZXMvUmVwb3NpdG9yeVNlcnZpY2VSZXNvdXJjZVR5cGU\u003d", + "datasourceName":"PSNC Institutional Repository", + "nsPrefix":"psnc______pl" +} \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/sequence_file b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/sequence_file new file mode 100644 index 0000000000000000000000000000000000000000..309645a5f2526ed6da0127e67a0664f7b79d96ff GIT binary patch literal 52308 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 z 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 Date: Tue, 2 Feb 2021 10:39:47 +0100 Subject: [PATCH 080/445] added method to list the known vocabulary names --- .../eu/dnetlib/dhp/common/vocabulary/VocabularyGroup.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary/VocabularyGroup.java b/dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary/VocabularyGroup.java index fac55189b..f81181e53 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary/VocabularyGroup.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary/VocabularyGroup.java @@ -67,6 +67,10 @@ public class VocabularyGroup implements Serializable { private final Map vocs = new HashMap<>(); + public Set vocabularyNames() { + return vocs.keySet(); + } + public void addVocabulary(final String id, final String name) { vocs.put(id.toLowerCase(), new Vocabulary(id, name)); } From d62ea1490d494393a730cf10ec61d592ca21e4a5 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 2 Feb 2021 10:53:19 +0100 Subject: [PATCH 081/445] cleaned up RabbitMQ stuff --- dhp-common/pom.xml | 5 - .../main/java/eu/dnetlib/message/Message.java | 76 ---------- .../eu/dnetlib/message/MessageConsumer.java | 47 ------ .../eu/dnetlib/message/MessageManager.java | 136 ------------------ .../java/eu/dnetlib/message/MessageType.java | 6 - .../java/eu/dnetlib/message/MessageTest.java | 51 ------- pom.xml | 5 - 7 files changed, 326 deletions(-) delete mode 100644 dhp-common/src/main/java/eu/dnetlib/message/Message.java delete mode 100644 dhp-common/src/main/java/eu/dnetlib/message/MessageConsumer.java delete mode 100644 dhp-common/src/main/java/eu/dnetlib/message/MessageManager.java delete mode 100644 dhp-common/src/main/java/eu/dnetlib/message/MessageType.java delete mode 100644 dhp-common/src/test/java/eu/dnetlib/message/MessageTest.java diff --git a/dhp-common/pom.xml b/dhp-common/pom.xml index 6eb2e0358..a8607a9b3 100644 --- a/dhp-common/pom.xml +++ b/dhp-common/pom.xml @@ -53,11 +53,6 @@ com.fasterxml.jackson.core jackson-databind - - - com.rabbitmq - amqp-client - net.sf.saxon Saxon-HE diff --git a/dhp-common/src/main/java/eu/dnetlib/message/Message.java b/dhp-common/src/main/java/eu/dnetlib/message/Message.java deleted file mode 100644 index fc1c38291..000000000 --- a/dhp-common/src/main/java/eu/dnetlib/message/Message.java +++ /dev/null @@ -1,76 +0,0 @@ - -package eu.dnetlib.message; - -import java.io.IOException; -import java.util.Map; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -public class Message { - - private String workflowId; - - private String jobName; - - private MessageType type; - - private Map body; - - public static Message fromJson(final String json) throws IOException { - final ObjectMapper jsonMapper = new ObjectMapper(); - return jsonMapper.readValue(json, Message.class); - } - - public Message() { - } - - public Message(String workflowId, String jobName, MessageType type, Map body) { - this.workflowId = workflowId; - this.jobName = jobName; - this.type = type; - this.body = body; - } - - public String getWorkflowId() { - return workflowId; - } - - public void setWorkflowId(String workflowId) { - this.workflowId = workflowId; - } - - public String getJobName() { - return jobName; - } - - public void setJobName(String jobName) { - this.jobName = jobName; - } - - public MessageType getType() { - return type; - } - - public void setType(MessageType type) { - this.type = type; - } - - public Map getBody() { - return body; - } - - public void setBody(Map body) { - this.body = body; - } - - @Override - public String toString() { - final ObjectMapper jsonMapper = new ObjectMapper(); - try { - return jsonMapper.writeValueAsString(this); - } catch (JsonProcessingException e) { - return null; - } - } -} diff --git a/dhp-common/src/main/java/eu/dnetlib/message/MessageConsumer.java b/dhp-common/src/main/java/eu/dnetlib/message/MessageConsumer.java deleted file mode 100644 index fb3f0bd95..000000000 --- a/dhp-common/src/main/java/eu/dnetlib/message/MessageConsumer.java +++ /dev/null @@ -1,47 +0,0 @@ - -package eu.dnetlib.message; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.LinkedBlockingQueue; - -import com.rabbitmq.client.AMQP; -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.DefaultConsumer; -import com.rabbitmq.client.Envelope; - -public class MessageConsumer extends DefaultConsumer { - - final LinkedBlockingQueue queueMessages; - - /** - * Constructs a new instance and records its association to the passed-in channel. - * - * @param channel the channel to which this consumer is attached - * @param queueMessages - */ - public MessageConsumer(Channel channel, LinkedBlockingQueue queueMessages) { - super(channel); - this.queueMessages = queueMessages; - } - - @Override - public void handleDelivery( - String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) - throws IOException { - final String json = new String(body, StandardCharsets.UTF_8); - Message message = Message.fromJson(json); - try { - this.queueMessages.put(message); - System.out.println("Receiving Message " + message); - } catch (InterruptedException e) { - if (message.getType() == MessageType.REPORT) - throw new RuntimeException("Error on sending message"); - else { - // TODO LOGGING EXCEPTION - } - } finally { - getChannel().basicAck(envelope.getDeliveryTag(), false); - } - } -} diff --git a/dhp-common/src/main/java/eu/dnetlib/message/MessageManager.java b/dhp-common/src/main/java/eu/dnetlib/message/MessageManager.java deleted file mode 100644 index 5ca79f3cc..000000000 --- a/dhp-common/src/main/java/eu/dnetlib/message/MessageManager.java +++ /dev/null @@ -1,136 +0,0 @@ - -package eu.dnetlib.message; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeoutException; - -import com.rabbitmq.client.Channel; -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; - -public class MessageManager { - - private final String messageHost; - - private final String username; - - private final String password; - - private Connection connection; - - private final Map channels = new HashMap<>(); - - private boolean durable; - - private boolean autodelete; - - private final LinkedBlockingQueue queueMessages; - - public MessageManager( - String messageHost, - String username, - String password, - final LinkedBlockingQueue queueMessages) { - this.queueMessages = queueMessages; - this.messageHost = messageHost; - this.username = username; - this.password = password; - } - - public MessageManager( - String messageHost, - String username, - String password, - boolean durable, - boolean autodelete, - final LinkedBlockingQueue queueMessages) { - this.queueMessages = queueMessages; - this.messageHost = messageHost; - this.username = username; - this.password = password; - - this.durable = durable; - this.autodelete = autodelete; - } - - private Connection createConnection() throws IOException, TimeoutException { - ConnectionFactory factory = new ConnectionFactory(); - factory.setHost(this.messageHost); - factory.setUsername(this.username); - factory.setPassword(this.password); - return factory.newConnection(); - } - - private Channel createChannel( - final Connection connection, - final String queueName, - final boolean durable, - final boolean autodelete) - throws Exception { - Map args = new HashMap<>(); - args.put("x-message-ttl", 10000); - Channel channel = connection.createChannel(); - channel.queueDeclare(queueName, durable, false, this.autodelete, args); - return channel; - } - - private Channel getOrCreateChannel(final String queueName, boolean durable, boolean autodelete) - throws Exception { - if (channels.containsKey(queueName)) { - return channels.get(queueName); - } - - if (this.connection == null) { - this.connection = createConnection(); - } - channels.put(queueName, createChannel(this.connection, queueName, durable, autodelete)); - return channels.get(queueName); - } - - public void close() throws IOException { - channels - .values() - .forEach( - ch -> { - try { - ch.close(); - } catch (Exception e) { - // TODO LOG - } - }); - - this.connection.close(); - } - - public boolean sendMessage(final Message message, String queueName) throws Exception { - try { - Channel channel = getOrCreateChannel(queueName, this.durable, this.autodelete); - channel.basicPublish("", queueName, null, message.toString().getBytes()); - return true; - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - - public boolean sendMessage( - final Message message, String queueName, boolean durable_var, boolean autodelete_var) - throws Exception { - try { - Channel channel = getOrCreateChannel(queueName, durable_var, autodelete_var); - channel.basicPublish("", queueName, null, message.toString().getBytes()); - return true; - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - - public void startConsumingMessage( - final String queueName, final boolean durable, final boolean autodelete) throws Exception { - - Channel channel = createChannel(createConnection(), queueName, durable, autodelete); - channel.basicConsume(queueName, false, new MessageConsumer(channel, queueMessages)); - } -} diff --git a/dhp-common/src/main/java/eu/dnetlib/message/MessageType.java b/dhp-common/src/main/java/eu/dnetlib/message/MessageType.java deleted file mode 100644 index 72cbda252..000000000 --- a/dhp-common/src/main/java/eu/dnetlib/message/MessageType.java +++ /dev/null @@ -1,6 +0,0 @@ - -package eu.dnetlib.message; - -public enum MessageType { - ONGOING, REPORT -} diff --git a/dhp-common/src/test/java/eu/dnetlib/message/MessageTest.java b/dhp-common/src/test/java/eu/dnetlib/message/MessageTest.java deleted file mode 100644 index 442f7b5c2..000000000 --- a/dhp-common/src/test/java/eu/dnetlib/message/MessageTest.java +++ /dev/null @@ -1,51 +0,0 @@ - -package eu.dnetlib.message; - -import static org.junit.jupiter.api.Assertions.*; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -public class MessageTest { - - @Test - public void fromJsonTest() throws IOException { - Message m = new Message(); - m.setWorkflowId("wId"); - m.setType(MessageType.ONGOING); - m.setJobName("Collection"); - Map body = new HashMap<>(); - body.put("parsedItem", "300"); - body.put("ExecutionTime", "30s"); - - m.setBody(body); - System.out.println("m = " + m); - Message m1 = Message.fromJson(m.toString()); - assertEquals(m1.getWorkflowId(), m.getWorkflowId()); - assertEquals(m1.getType(), m.getType()); - assertEquals(m1.getJobName(), m.getJobName()); - - assertNotNull(m1.getBody()); - m1.getBody().keySet().forEach(it -> assertEquals(m1.getBody().get(it), m.getBody().get(it))); - assertEquals(m1.getJobName(), m.getJobName()); - } - - @Test - public void toStringTest() { - final String expectedJson = "{\"workflowId\":\"wId\",\"jobName\":\"Collection\",\"type\":\"ONGOING\",\"body\":{\"ExecutionTime\":\"30s\",\"parsedItem\":\"300\"}}"; - Message m = new Message(); - m.setWorkflowId("wId"); - m.setType(MessageType.ONGOING); - m.setJobName("Collection"); - Map body = new HashMap<>(); - body.put("parsedItem", "300"); - body.put("ExecutionTime", "30s"); - - m.setBody(body); - - assertEquals(expectedJson, m.toString()); - } -} diff --git a/pom.xml b/pom.xml index 3e0626aed..cfe1edfbd 100644 --- a/pom.xml +++ b/pom.xml @@ -374,11 +374,6 @@ provided - - com.rabbitmq - amqp-client - 5.6.0 - com.jayway.jsonpath json-path From 0634674add8c18d393e65ef68d200ba2be3bd6da Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Tue, 2 Feb 2021 12:12:14 +0100 Subject: [PATCH 082/445] implemented transformation test --- .../GenerateDataciteDatasetSpark.scala | 2 +- .../transformation/TransformSparkJobNode.java | 15 +- .../transformation/TransformationFactory.java | 4 +- .../oozie_app/config-default.xml | 5 +- .../dhp/transformation/oozie_app/workflow.xml | 53 ++++- .../dhp/aggregation/AggregationJobTest.java | 197 ++++++++++++++++++ .../GenerateNativeStoreSparkJobTest.java | 169 --------------- .../transformation/TransformationJobTest.java | 4 + .../dhp/collection/mdStoreCleanedVersion.json | 9 + 9 files changed, 275 insertions(+), 183 deletions(-) create mode 100644 dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java delete mode 100644 dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJobTest.java create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/mdStoreCleanedVersion.json diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/GenerateDataciteDatasetSpark.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/GenerateDataciteDatasetSpark.scala index 6837e94b2..f04f92c63 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/GenerateDataciteDatasetSpark.scala +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/GenerateDataciteDatasetSpark.scala @@ -27,7 +27,7 @@ object GenerateDataciteDatasetSpark { val isLookupService = ISLookupClientFactory.getLookUpService(isLookupUrl) val vocabularies = VocabularyGroup.loadVocsFromIS(isLookupService) - + log.info(s"vocabulary size is ${vocabularies.getTerms("dnet:languages").size()}") val spark: SparkSession = SparkSession.builder().config(conf) .appName(GenerateDataciteDatasetSpark.getClass.getSimpleName) .master(master) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java index b9df902a1..193da3878 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java @@ -24,6 +24,7 @@ import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; import eu.dnetlib.dhp.aggregation.common.AggregationCounter; import eu.dnetlib.dhp.aggregation.common.AggregationUtility; import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; @@ -60,15 +61,23 @@ public class TransformSparkJobNode { final String isLookupUrl = parser.get("isLookupUrl"); log.info(String.format("isLookupUrl: %s", isLookupUrl)); + final String dateOfTransformation = parser.get("dateOfTransformation"); + log.info(String.format("dateOfTransformation: %s", dateOfTransformation)); + + final ISLookUpService isLookupService = ISLookupClientFactory.getLookUpService(isLookupUrl); + final VocabularyGroup vocabularies = VocabularyGroup.loadVocsFromIS(isLookupService); + + log.info("Retrieved {} vocabularies", vocabularies.vocabularyNames().size()); + SparkConf conf = new SparkConf(); runWithSparkSession( conf, isSparkSessionManaged, spark -> transformRecords( - parser.getObjectMap(), isLookupService, spark, nativeMdStoreVersion.getHdfsPath(), - cleanedMdStoreVersion.getHdfsPath())); + parser.getObjectMap(), isLookupService, spark, nativeMdStoreVersion.getHdfsPath() + "/store", + cleanedMdStoreVersion.getHdfsPath() + "/store")); } public static void transformRecords(final Map args, final ISLookUpService isLookUpService, @@ -82,7 +91,7 @@ public class TransformSparkJobNode { final Encoder encoder = Encoders.bean(MetadataRecord.class); final Dataset mdstoreInput = spark.read().format("parquet").load(inputPath).as(encoder); final MapFunction XSLTTransformationFunction = TransformationFactory - .getTransformationPlugin(args, ct, isLookUpService); + .getTransformationPlugin(args, ct, isLookUpService); mdstoreInput.map(XSLTTransformationFunction, encoder).write().save(outputPath + "/store"); log.info("Transformed item " + ct.getProcessedItems().count()); diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java index d1f896964..45ba2981f 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java @@ -18,7 +18,7 @@ import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; public class TransformationFactory { private static final Logger log = LoggerFactory.getLogger(TransformationFactory.class); - public static final String TRULE_XQUERY = "for $x in collection('/db/DRIVER/TransformationRuleDSResources/TransformationRuleDSResourceType') where $x//RESOURCE_IDENTIFIER/@value = \"%s\" return $x//CODE/text()"; + public static final String TRULE_XQUERY = "for $x in collection('/db/DRIVER/TransformationRuleDSResources/TransformationRuleDSResourceType') where $x//RESOURCE_IDENTIFIER/@value = \"%s\" return $x//CODE/*[local-name() =\"stylesheet\"]"; public static MapFunction getTransformationPlugin( final Map jobArgument, final AggregationCounter counters, final ISLookUpService isLookupService) @@ -57,7 +57,7 @@ public class TransformationFactory { private static String queryTransformationRuleFromIS(final String transformationRuleId, final ISLookUpService isLookUpService) throws Exception { final String query = String.format(TRULE_XQUERY, transformationRuleId); - log.info("asking query to IS: " + query); + System.out.println("asking query to IS: " + query); List result = isLookUpService.quickSearchProfile(query); if (result == null || result.isEmpty()) diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/config-default.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/config-default.xml index e77dd09c9..bdd48b0ab 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/config-default.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/config-default.xml @@ -15,8 +15,5 @@ oozie.action.sharelib.for.spark spark2 - - oozie.launcher.mapreduce.user.classpath.first - true - + \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml index aff87dc79..43b270eaf 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml @@ -18,12 +18,17 @@ transformationPlugin + XSLT_TRANSFORM The transformation Plugin dateOfTransformation The timestamp of the transformation date + + isLookupUrl + The IS lookUp service endopoint + @@ -35,22 +40,36 @@ + + + oozie.launcher.mapreduce.user.classpath.first + true + + eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode --actionREAD_LOCK --mdStoreID${mdStoreInputId} --mdStoreManagerURI${mdStoreManagerURI} + + + + oozie.launcher.mapreduce.user.classpath.first + true + + eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode --actionNEW_VERSION --mdStoreID${mdStoreOutputId} --mdStoreManagerURI${mdStoreManagerURI} + @@ -62,7 +81,7 @@ cluster Transform MetadataStore eu.dnetlib.dhp.transformation.TransformSparkJobNode - dhp-aggregations-${projectVersion}.jar + dhp-aggregation-${projectVersion}.jar --executor-memory=${sparkExecutorMemory} --executor-cores=${sparkExecutorCores} @@ -72,11 +91,12 @@ --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} - --mdstoreInputVersion${wf:actionData('StartTransaction')['mdStoreVersion']} - --mdstoreOutputVersion${wf:actionData('BeginRead')['mdStoreReadLockVersion']} + --mdstoreOutputVersion${wf:actionData('StartTransaction')['mdStoreVersion']} + --mdstoreInputVersion${wf:actionData('BeginRead')['mdStoreReadLockVersion']} --dateOfTransformation${dateOfTransformation} --transformationPlugin${transformationPlugin} --transformationRuleId${transformationRuleId} + --isLookupUrl${isLookupUrl} @@ -84,6 +104,13 @@ + + + oozie.launcher.mapreduce.user.classpath.first + true + + + eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode --actionREAD_UNLOCK --mdStoreManagerURI${mdStoreManagerURI} @@ -96,6 +123,12 @@ + + + oozie.launcher.mapreduce.user.classpath.first + true + + eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode --actionCOMMIT --namenode${nameNode} @@ -108,18 +141,30 @@ + + + oozie.launcher.mapreduce.user.classpath.first + true + + eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode --actionREAD_UNLOCK --mdStoreManagerURI${mdStoreManagerURI} --readMDStoreId${wf:actionData('BeginRead')['mdStoreReadLockVersion']} - + + + + oozie.launcher.mapreduce.user.classpath.first + true + + eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode --actionROLLBACK --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java new file mode 100644 index 000000000..c9ccbc7ff --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java @@ -0,0 +1,197 @@ + +package eu.dnetlib.dhp.aggregation; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import eu.dnetlib.dhp.collection.GenerateNativeStoreSparkJob; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; +import eu.dnetlib.dhp.transformation.TransformSparkJobNode; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.hadoop.io.IntWritable; +import org.apache.hadoop.io.Text; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaSparkContext; +import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoder; +import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; +import eu.dnetlib.dhp.model.mdstore.MetadataRecord; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class AggregationJobTest { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private static SparkSession spark; + + private static Path workingDir; + + private static Encoder encoder; + + private static final String encoding = "XML"; + private static final String dateOfCollection = System.currentTimeMillis() + ""; + private static final String xpath = "//*[local-name()='header']/*[local-name()='identifier']"; + private static String provenance; + + private static final Logger log = LoggerFactory.getLogger(AggregationJobTest.class); + + @BeforeAll + public static void beforeAll() throws IOException { + provenance = IOUtils.toString(AggregationJobTest.class.getResourceAsStream("/eu/dnetlib/dhp/collection/provenance.json")); + workingDir = Files.createTempDirectory(AggregationJobTest.class.getSimpleName()); + log.info("using work dir {}", workingDir); + + SparkConf conf = new SparkConf(); + + conf.setAppName(AggregationJobTest.class.getSimpleName()); + + conf.setMaster("local[*]"); + conf.set("spark.driver.host", "localhost"); + conf.set("hive.metastore.local", "true"); + conf.set("spark.ui.enabled", "false"); + conf.set("spark.sql.warehouse.dir", workingDir.toString()); + conf.set("hive.metastore.warehouse.dir", workingDir.resolve("warehouse").toString()); + + encoder = Encoders.bean(MetadataRecord.class); + spark = SparkSession + .builder() + .appName(AggregationJobTest.class.getSimpleName()) + .config(conf) + .getOrCreate(); + } + + @AfterAll + public static void afterAll() throws IOException { + FileUtils.deleteDirectory(workingDir.toFile()); + spark.stop(); + } + + @Test + @Order(1) + public void testGenerateNativeStoreSparkJobRefresh() throws Exception { + + MDStoreVersion mdStoreV1 = prepareVersion("/eu/dnetlib/dhp/collection/mdStoreVersion_1.json"); + FileUtils.forceMkdir(new File(mdStoreV1.getHdfsPath())); + + IOUtils + .copy( + getClass().getResourceAsStream("/eu/dnetlib/dhp/collection/sequence_file"), + new FileOutputStream(mdStoreV1.getHdfsPath() + "/sequence_file")); + + GenerateNativeStoreSparkJob + .main( + new String[]{ + "-isSparkSessionManaged", Boolean.FALSE.toString(), + "-encoding", encoding, + "-dateOfCollection", dateOfCollection, + "-provenance", provenance, + "-xpath", xpath, + "-mdStoreVersion", OBJECT_MAPPER.writeValueAsString(mdStoreV1), + "-readMdStoreVersion", "", + "-workflowId", "abc" + }); + + verify(mdStoreV1); + } + + @Test + @Order(2) + public void testGenerateNativeStoreSparkJobIncremental() throws Exception { + + MDStoreVersion mdStoreV2 = prepareVersion("/eu/dnetlib/dhp/collection/mdStoreVersion_2.json"); + FileUtils.forceMkdir(new File(mdStoreV2.getHdfsPath())); + + IOUtils + .copy( + getClass().getResourceAsStream("/eu/dnetlib/dhp/collection/sequence_file"), + new FileOutputStream(mdStoreV2.getHdfsPath() + "/sequence_file")); + + MDStoreVersion mdStoreV1 = prepareVersion("/eu/dnetlib/dhp/collection/mdStoreVersion_1.json"); + + GenerateNativeStoreSparkJob + .main( + new String[]{ + "-isSparkSessionManaged", Boolean.FALSE.toString(), + "-encoding", encoding, + "-dateOfCollection", dateOfCollection, + "-provenance", provenance, + "-xpath", xpath, + "-mdStoreVersion", OBJECT_MAPPER.writeValueAsString(mdStoreV2), + "-readMdStoreVersion", OBJECT_MAPPER.writeValueAsString(mdStoreV1), + "-workflowId", "abc" + }); + + verify(mdStoreV2); + } + + + //@Test + @Order(3) + public void testTransformSparkJob() throws Exception { + + MDStoreVersion mdStoreV2 = prepareVersion("/eu/dnetlib/dhp/collection/mdStoreVersion_2.json"); + MDStoreVersion mdStoreCleanedVersion = prepareVersion("/eu/dnetlib/dhp/collection/mdStoreCleanedVersion.json"); + + TransformSparkJobNode.main(new String[]{ + "-isSparkSessionManaged", Boolean.FALSE.toString(), + "-dateOfTransformation", dateOfCollection, + "-mdstoreInputVersion", OBJECT_MAPPER.writeValueAsString(mdStoreV2), + "-mdstoreOutputVersion", OBJECT_MAPPER.writeValueAsString(mdStoreCleanedVersion), + "-transformationPlugin", "XSLT_TRANSFORM", + "-isLookupUrl", "https://dev-openaire.d4science.org/is/services/isLookUp", + "-transformationRuleId", "183dde52-a69b-4db9-a07e-1ef2be105294_VHJhbnNmb3JtYXRpb25SdWxlRFNSZXNvdXJjZXMvVHJhbnNmb3JtYXRpb25SdWxlRFNSZXNvdXJjZVR5cGU="}); + + } + + protected void verify(MDStoreVersion mdStoreVersion) throws IOException { + Assertions.assertTrue(new File(mdStoreVersion.getHdfsPath()).exists()); + + final JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); + long seqFileSize = sc + .sequenceFile(mdStoreVersion.getHdfsPath() + "/sequence_file", IntWritable.class, Text.class) + .count(); + + final Dataset mdstore = spark.read().load(mdStoreVersion.getHdfsPath() + "/store").as(encoder); + long mdStoreSize = mdstore.count(); + + long declaredSize = Long.parseLong(IOUtils.toString(new FileReader(mdStoreVersion.getHdfsPath() + "/size"))); + + Assertions.assertEquals(seqFileSize, declaredSize, "the size must be equal"); + Assertions.assertEquals(seqFileSize, mdStoreSize, "the size must be equal"); + + long uniqueIds = mdstore + .map((MapFunction) MetadataRecord::getId, Encoders.STRING()) + .distinct() + .count(); + + Assertions.assertEquals(seqFileSize, uniqueIds, "the size must be equal"); + } + + private MDStoreVersion prepareVersion(String filename) throws IOException { + MDStoreVersion mdstore = OBJECT_MAPPER + .readValue(IOUtils.toString(getClass().getResource(filename)), MDStoreVersion.class); + mdstore.setHdfsPath(String.format(mdstore.getHdfsPath(), workingDir.toString())); + return mdstore; + } + +} diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJobTest.java deleted file mode 100644 index 715ad8fa6..000000000 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJobTest.java +++ /dev/null @@ -1,169 +0,0 @@ - -package eu.dnetlib.dhp.collection; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import org.apache.hadoop.io.IntWritable; -import org.apache.hadoop.io.Text; -import org.apache.spark.SparkConf; -import org.apache.spark.api.java.JavaSparkContext; -import org.apache.spark.api.java.function.MapFunction; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Encoder; -import org.apache.spark.sql.Encoders; -import org.apache.spark.sql.SparkSession; -import org.junit.jupiter.api.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; -import eu.dnetlib.dhp.model.mdstore.MetadataRecord; - -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) -public class GenerateNativeStoreSparkJobTest { - - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - private static SparkSession spark; - - private static Path workingDir; - - private static Encoder encoder; - - private static final String encoding = "XML"; - private static final String dateOfCollection = System.currentTimeMillis() + ""; - private static final String xpath = "//*[local-name()='header']/*[local-name()='identifier']"; - private static String provenance; - - private static final Logger log = LoggerFactory.getLogger(GenerateNativeStoreSparkJobTest.class); - - @BeforeAll - public static void beforeAll() throws IOException { - provenance = IOUtils.toString(GenerateNativeStoreSparkJobTest.class.getResourceAsStream("provenance.json")); - workingDir = Files.createTempDirectory(GenerateNativeStoreSparkJobTest.class.getSimpleName()); - log.info("using work dir {}", workingDir); - - SparkConf conf = new SparkConf(); - - conf.setAppName(GenerateNativeStoreSparkJobTest.class.getSimpleName()); - - conf.setMaster("local[*]"); - conf.set("spark.driver.host", "localhost"); - conf.set("hive.metastore.local", "true"); - conf.set("spark.ui.enabled", "false"); - conf.set("spark.sql.warehouse.dir", workingDir.toString()); - conf.set("hive.metastore.warehouse.dir", workingDir.resolve("warehouse").toString()); - - encoder = Encoders.bean(MetadataRecord.class); - spark = SparkSession - .builder() - .appName(GenerateNativeStoreSparkJobTest.class.getSimpleName()) - .config(conf) - .getOrCreate(); - } - - @AfterAll - public static void afterAll() throws IOException { - FileUtils.deleteDirectory(workingDir.toFile()); - spark.stop(); - } - - @Test - @Order(1) - public void testGenerateNativeStoreSparkJobRefresh() throws Exception { - - MDStoreVersion mdStoreV1 = prepareVersion("mdStoreVersion_1.json"); - FileUtils.forceMkdir(new File(mdStoreV1.getHdfsPath())); - - IOUtils - .copy( - getClass().getResourceAsStream("sequence_file"), - new FileOutputStream(mdStoreV1.getHdfsPath() + "/sequence_file")); - - GenerateNativeStoreSparkJob - .main( - new String[] { - "-isSparkSessionManaged", Boolean.FALSE.toString(), - "-encoding", encoding, - "-dateOfCollection", dateOfCollection, - "-provenance", provenance, - "-xpath", xpath, - "-mdStoreVersion", OBJECT_MAPPER.writeValueAsString(mdStoreV1), - "-readMdStoreVersion", "", - "-workflowId", "abc" - }); - - verify(mdStoreV1); - } - - @Test - @Order(2) - public void testGenerateNativeStoreSparkJobIncremental() throws Exception { - - MDStoreVersion mdStoreV2 = prepareVersion("mdStoreVersion_2.json"); - FileUtils.forceMkdir(new File(mdStoreV2.getHdfsPath())); - - IOUtils - .copy( - getClass().getResourceAsStream("sequence_file"), - new FileOutputStream(mdStoreV2.getHdfsPath() + "/sequence_file")); - - MDStoreVersion mdStoreV1 = prepareVersion("mdStoreVersion_1.json"); - - GenerateNativeStoreSparkJob - .main( - new String[] { - "-isSparkSessionManaged", Boolean.FALSE.toString(), - "-encoding", encoding, - "-dateOfCollection", dateOfCollection, - "-provenance", provenance, - "-xpath", xpath, - "-mdStoreVersion", OBJECT_MAPPER.writeValueAsString(mdStoreV2), - "-readMdStoreVersion", OBJECT_MAPPER.writeValueAsString(mdStoreV1), - "-workflowId", "abc" - }); - - verify(mdStoreV2); - } - - protected void verify(MDStoreVersion mdStoreVersion) throws IOException { - Assertions.assertTrue(new File(mdStoreVersion.getHdfsPath()).exists()); - - final JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); - long seqFileSize = sc - .sequenceFile(mdStoreVersion.getHdfsPath() + "/sequence_file", IntWritable.class, Text.class) - .count(); - - final Dataset mdstore = spark.read().load(mdStoreVersion.getHdfsPath() + "/store").as(encoder); - long mdStoreSize = mdstore.count(); - - long declaredSize = Long.parseLong(IOUtils.toString(new FileReader(mdStoreVersion.getHdfsPath() + "/size"))); - - Assertions.assertEquals(seqFileSize, declaredSize, "the size must be equal"); - Assertions.assertEquals(seqFileSize, mdStoreSize, "the size must be equal"); - - long uniqueIds = mdstore - .map((MapFunction) MetadataRecord::getId, Encoders.STRING()) - .distinct() - .count(); - - Assertions.assertEquals(seqFileSize, uniqueIds, "the size must be equal"); - } - - private MDStoreVersion prepareVersion(String filename) throws IOException { - MDStoreVersion mdstore = OBJECT_MAPPER - .readValue(IOUtils.toString(getClass().getResource(filename)), MDStoreVersion.class); - mdstore.setHdfsPath(String.format(mdstore.getHdfsPath(), workingDir.toString())); - return mdstore; - } - -} diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java index 6a80e01e2..9e46b5f95 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java @@ -38,6 +38,7 @@ import eu.dnetlib.dhp.collection.CollectionJobTest; import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; import eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; @@ -74,6 +75,9 @@ public class TransformationJobTest { spark.stop(); } + + + @Test @DisplayName("Test Transform Single XML using XSLTTransformator") public void testTransformSaxonHE() throws Exception { diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/mdStoreCleanedVersion.json b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/mdStoreCleanedVersion.json new file mode 100644 index 000000000..a5adc8fda --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/mdStoreCleanedVersion.json @@ -0,0 +1,9 @@ +{ + "id":"md-cleaned", + "mdstore":"md-cleaned", + "writing":false, + "readCount":1, + "lastUpdate":1612187563099, + "size":71, + "hdfsPath":"%s/mdstore/md-cleaned" +} \ No newline at end of file From 75807ea5ae69a1776b65ff3b31a18db127e80835 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 2 Feb 2021 12:28:21 +0100 Subject: [PATCH 083/445] factored out constants --- .../common/AggregationConstants.java | 15 +++++ .../common/AggregationUtility.java | 3 + .../GenerateNativeStoreSparkJob.java | 55 +++++++------------ .../worker/CollectorWorkerApplication.java | 4 +- .../transformation/TransformSparkJobNode.java | 32 +++++++---- .../dhp/aggregation/AggregationJobTest.java | 2 +- 6 files changed, 63 insertions(+), 48 deletions(-) create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationConstants.java diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationConstants.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationConstants.java new file mode 100644 index 000000000..15e0bb454 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationConstants.java @@ -0,0 +1,15 @@ +package eu.dnetlib.dhp.aggregation.common; + +public class AggregationConstants { + + public static final String SEQUENCE_FILE_NAME = "/sequence_file"; + public static final String MDSTORE_DATA_PATH = "/store"; + public static final String MDSTORE_SIZE_PATH = "/size"; + + public static final String CONTENT_TOTALITEMS = "TotalItems"; + public static final String CONTENT_INVALIDRECORDS = "InvalidRecords"; + public static final String CONTENT_TRANSFORMEDRECORDS = "transformedItems"; + + + +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java index eb971c475..d657dee02 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java @@ -5,6 +5,7 @@ import java.io.BufferedOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; @@ -21,6 +22,8 @@ public class AggregationUtility { private static final Logger log = LoggerFactory.getLogger(AggregationUtility.class); + public static final ObjectMapper MAPPER = new ObjectMapper(); + public static void writeTotalSizeOnHDFS(final SparkSession spark, final Long total, final String path) throws IOException { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java index bbed36a9c..13813623c 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java @@ -1,15 +1,11 @@ package eu.dnetlib.dhp.collection; -import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.*; -import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.model.mdstore.MetadataRecord; +import eu.dnetlib.dhp.model.mdstore.Provenance; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.io.IntWritable; @@ -26,26 +22,22 @@ import org.dom4j.Node; import org.dom4j.io.SAXReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; -import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.collection.worker.CollectorWorkerApplication; -import eu.dnetlib.dhp.model.mdstore.MetadataRecord; -import eu.dnetlib.dhp.model.mdstore.Provenance; -import net.sf.saxon.expr.Component; import scala.Tuple2; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.Optional; + +import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.*; +import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.*; +import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; + public class GenerateNativeStoreSparkJob { private static final Logger log = LoggerFactory.getLogger(GenerateNativeStoreSparkJob.class); - private static final ObjectMapper MAPPER = new ObjectMapper(); - - private static final String DATASET_NAME = "/store"; - public static void main(String[] args) throws Exception { final ArgumentApplicationParser parser = new ArgumentApplicationParser( @@ -88,11 +80,6 @@ public class GenerateNativeStoreSparkJob { log.info("isSparkSessionManaged: {}", isSparkSessionManaged); SparkConf conf = new SparkConf(); - /* - * conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer"); conf .registerKryoClasses( new - * Class[] { MetadataRecord.class, Provenance.class }); - */ - runWithSparkSession( conf, isSparkSessionManaged, @@ -109,10 +96,10 @@ public class GenerateNativeStoreSparkJob { MDStoreVersion readVersion) throws IOException { final JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); - final LongAccumulator totalItems = sc.sc().longAccumulator("TotalItems"); - final LongAccumulator invalidRecords = sc.sc().longAccumulator("InvalidRecords"); + final LongAccumulator totalItems = sc.sc().longAccumulator(CONTENT_TOTALITEMS); + final LongAccumulator invalidRecords = sc.sc().longAccumulator(CONTENT_INVALIDRECORDS); - final String seqFilePath = currentVersion.getHdfsPath() + CollectorWorkerApplication.SEQUENCE_FILE_NAME; + final String seqFilePath = currentVersion.getHdfsPath() + SEQUENCE_FILE_NAME; final JavaRDD nativeStore = sc .sequenceFile(seqFilePath, IntWritable.class, Text.class) .map( @@ -130,13 +117,13 @@ public class GenerateNativeStoreSparkJob { final Encoder encoder = Encoders.bean(MetadataRecord.class); final Dataset mdstore = spark.createDataset(nativeStore.rdd(), encoder); - final String targetPath = currentVersion.getHdfsPath() + DATASET_NAME; + final String targetPath = currentVersion.getHdfsPath() + MDSTORE_DATA_PATH; if (readVersion != null) { // INCREMENTAL MODE log.info("updating {} incrementally with {}", targetPath, readVersion.getHdfsPath()); Dataset currentMdStoreVersion = spark .read() - .load(readVersion.getHdfsPath() + DATASET_NAME) + .load(readVersion.getHdfsPath() + MDSTORE_DATA_PATH) .as(encoder); TypedColumn aggregator = new MDStoreAggregator().toColumn(); @@ -159,7 +146,7 @@ public class GenerateNativeStoreSparkJob { final Long total = spark.read().load(targetPath).count(); log.info("collected {} records for datasource '{}'", total, provenance.getDatasourceName()); - writeTotalSizeOnHDFS(spark, total, currentVersion.getHdfsPath() + "/size"); + writeTotalSizeOnHDFS(spark, total, currentVersion.getHdfsPath() + MDSTORE_SIZE_PATH); } public static class MDStoreAggregator extends Aggregator { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java index e24b9ad1d..da5b197d6 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java @@ -1,6 +1,8 @@ package eu.dnetlib.dhp.collection.worker; +import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.*; + import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,8 +27,6 @@ public class CollectorWorkerApplication { private static final CollectorPluginFactory collectorPluginFactory = new CollectorPluginFactory(); - public static String SEQUENCE_FILE_NAME = "/sequence_file"; - /** * @param args */ diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java index 193da3878..f8ddf47e2 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java @@ -2,14 +2,17 @@ package eu.dnetlib.dhp.transformation; import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; +import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.*; +import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.*; import java.io.IOException; import java.util.Map; import java.util.Optional; +import eu.dnetlib.dhp.aggregation.common.AggregationConstants; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; -import org.apache.spark.api.java.function.MapFunction; + import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoder; import org.apache.spark.sql.Encoders; @@ -76,29 +79,36 @@ public class TransformSparkJobNode { conf, isSparkSessionManaged, spark -> transformRecords( - parser.getObjectMap(), isLookupService, spark, nativeMdStoreVersion.getHdfsPath() + "/store", - cleanedMdStoreVersion.getHdfsPath() + "/store")); + parser.getObjectMap(), isLookupService, spark, nativeMdStoreVersion.getHdfsPath() + MDSTORE_DATA_PATH, + cleanedMdStoreVersion.getHdfsPath() + MDSTORE_DATA_PATH)); } public static void transformRecords(final Map args, final ISLookUpService isLookUpService, final SparkSession spark, final String inputPath, final String outputPath) throws DnetTransformationException, IOException { - final LongAccumulator totalItems = spark.sparkContext().longAccumulator("TotalItems"); - final LongAccumulator errorItems = spark.sparkContext().longAccumulator("errorItems"); - final LongAccumulator transformedItems = spark.sparkContext().longAccumulator("transformedItems"); + final LongAccumulator totalItems = spark.sparkContext().longAccumulator(CONTENT_TOTALITEMS); + final LongAccumulator errorItems = spark.sparkContext().longAccumulator(CONTENT_INVALIDRECORDS); + final LongAccumulator transformedItems = spark.sparkContext().longAccumulator(CONTENT_TRANSFORMEDRECORDS); final AggregationCounter ct = new AggregationCounter(totalItems, errorItems, transformedItems); final Encoder encoder = Encoders.bean(MetadataRecord.class); - final Dataset mdstoreInput = spark.read().format("parquet").load(inputPath).as(encoder); - final MapFunction XSLTTransformationFunction = TransformationFactory - .getTransformationPlugin(args, ct, isLookUpService); - mdstoreInput.map(XSLTTransformationFunction, encoder).write().save(outputPath + "/store"); + + saveDataset( + spark.read() + .format("parquet") + .load(inputPath) + .as(encoder) + .map( + TransformationFactory.getTransformationPlugin(args, ct, isLookUpService), + encoder), + outputPath + MDSTORE_DATA_PATH); + log.info("Transformed item " + ct.getProcessedItems().count()); log.info("Total item " + ct.getTotalItems().count()); log.info("Transformation Error item " + ct.getErrorItems().count()); - AggregationUtility.writeTotalSizeOnHDFS(spark, ct.getProcessedItems().count(), outputPath + "/size"); + writeTotalSizeOnHDFS(spark, ct.getProcessedItems().count(), outputPath + MDSTORE_SIZE_PATH); } } diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java index c9ccbc7ff..ac65ef6a9 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java @@ -145,7 +145,7 @@ public class AggregationJobTest { } - //@Test + @Test @Order(3) public void testTransformSparkJob() throws Exception { From bb89b99b24d4ad7e2bf05d383c87a74874af4929 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 2 Feb 2021 12:34:14 +0100 Subject: [PATCH 084/445] code formatting --- .../common/AggregationConstants.java | 15 +- .../common/AggregationUtility.java | 3 +- .../GenerateNativeStoreSparkJob.java | 32 +-- .../transformation/TransformSparkJobNode.java | 28 +- .../dhp/aggregation/AggregationJobTest.java | 250 +++++++++--------- .../transformation/TransformationJobTest.java | 3 - 6 files changed, 164 insertions(+), 167 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationConstants.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationConstants.java index 15e0bb454..7c5ad354d 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationConstants.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationConstants.java @@ -1,15 +1,14 @@ + package eu.dnetlib.dhp.aggregation.common; public class AggregationConstants { - public static final String SEQUENCE_FILE_NAME = "/sequence_file"; - public static final String MDSTORE_DATA_PATH = "/store"; - public static final String MDSTORE_SIZE_PATH = "/size"; - - public static final String CONTENT_TOTALITEMS = "TotalItems"; - public static final String CONTENT_INVALIDRECORDS = "InvalidRecords"; - public static final String CONTENT_TRANSFORMEDRECORDS = "transformedItems"; - + public static final String SEQUENCE_FILE_NAME = "/sequence_file"; + public static final String MDSTORE_DATA_PATH = "/store"; + public static final String MDSTORE_SIZE_PATH = "/size"; + public static final String CONTENT_TOTALITEMS = "TotalItems"; + public static final String CONTENT_INVALIDRECORDS = "InvalidRecords"; + public static final String CONTENT_TRANSFORMEDRECORDS = "transformedItems"; } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java index d657dee02..7332ac071 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java @@ -5,7 +5,6 @@ import java.io.BufferedOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; -import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; @@ -15,6 +14,8 @@ import org.apache.spark.sql.SparkSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.databind.ObjectMapper; + import eu.dnetlib.dhp.collection.GenerateNativeStoreSparkJob; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java index 13813623c..fdf3965d6 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java @@ -1,11 +1,16 @@ package eu.dnetlib.dhp.collection; -import com.fasterxml.jackson.databind.ObjectMapper; -import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; -import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.model.mdstore.MetadataRecord; -import eu.dnetlib.dhp.model.mdstore.Provenance; +import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.*; +import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.*; +import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.Optional; + import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.io.IntWritable; @@ -22,18 +27,15 @@ import org.dom4j.Node; import org.dom4j.io.SAXReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.model.mdstore.MetadataRecord; +import eu.dnetlib.dhp.model.mdstore.Provenance; import scala.Tuple2; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Objects; -import java.util.Optional; - -import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.*; -import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.*; -import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; - public class GenerateNativeStoreSparkJob { private static final Logger log = LoggerFactory.getLogger(GenerateNativeStoreSparkJob.class); diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java index f8ddf47e2..0a01faf1e 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java @@ -1,19 +1,17 @@ package eu.dnetlib.dhp.transformation; -import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; -import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.*; import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.*; +import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.saveDataset; +import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.writeTotalSizeOnHDFS; +import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; import java.io.IOException; import java.util.Map; import java.util.Optional; -import eu.dnetlib.dhp.aggregation.common.AggregationConstants; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; - -import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoder; import org.apache.spark.sql.Encoders; import org.apache.spark.sql.SparkSession; @@ -25,7 +23,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; import eu.dnetlib.dhp.aggregation.common.AggregationCounter; -import eu.dnetlib.dhp.aggregation.common.AggregationUtility; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; @@ -67,7 +64,6 @@ public class TransformSparkJobNode { final String dateOfTransformation = parser.get("dateOfTransformation"); log.info(String.format("dateOfTransformation: %s", dateOfTransformation)); - final ISLookUpService isLookupService = ISLookupClientFactory.getLookUpService(isLookupUrl); final VocabularyGroup vocabularies = VocabularyGroup.loadVocsFromIS(isLookupService); @@ -94,15 +90,15 @@ public class TransformSparkJobNode { final Encoder encoder = Encoders.bean(MetadataRecord.class); saveDataset( - spark.read() - .format("parquet") - .load(inputPath) - .as(encoder) - .map( - TransformationFactory.getTransformationPlugin(args, ct, isLookUpService), - encoder), - outputPath + MDSTORE_DATA_PATH); - + spark + .read() + .format("parquet") + .load(inputPath) + .as(encoder) + .map( + TransformationFactory.getTransformationPlugin(args, ct, isLookUpService), + encoder), + outputPath + MDSTORE_DATA_PATH); log.info("Transformed item " + ct.getProcessedItems().count()); log.info("Total item " + ct.getTotalItems().count()); diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java index ac65ef6a9..d5ecc9cb0 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java @@ -12,11 +12,6 @@ import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; -import eu.dnetlib.dhp.collection.GenerateNativeStoreSparkJob; -import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; -import eu.dnetlib.dhp.transformation.TransformSparkJobNode; -import eu.dnetlib.dhp.utils.ISLookupClientFactory; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.hadoop.io.IntWritable; @@ -35,163 +30,170 @@ import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; +import eu.dnetlib.dhp.collection.GenerateNativeStoreSparkJob; +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; +import eu.dnetlib.dhp.transformation.TransformSparkJobNode; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class AggregationJobTest { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private static SparkSession spark; + private static SparkSession spark; - private static Path workingDir; + private static Path workingDir; - private static Encoder encoder; + private static Encoder encoder; - private static final String encoding = "XML"; - private static final String dateOfCollection = System.currentTimeMillis() + ""; - private static final String xpath = "//*[local-name()='header']/*[local-name()='identifier']"; - private static String provenance; + private static final String encoding = "XML"; + private static final String dateOfCollection = System.currentTimeMillis() + ""; + private static final String xpath = "//*[local-name()='header']/*[local-name()='identifier']"; + private static String provenance; - private static final Logger log = LoggerFactory.getLogger(AggregationJobTest.class); + private static final Logger log = LoggerFactory.getLogger(AggregationJobTest.class); - @BeforeAll - public static void beforeAll() throws IOException { - provenance = IOUtils.toString(AggregationJobTest.class.getResourceAsStream("/eu/dnetlib/dhp/collection/provenance.json")); - workingDir = Files.createTempDirectory(AggregationJobTest.class.getSimpleName()); - log.info("using work dir {}", workingDir); + @BeforeAll + public static void beforeAll() throws IOException { + provenance = IOUtils + .toString(AggregationJobTest.class.getResourceAsStream("/eu/dnetlib/dhp/collection/provenance.json")); + workingDir = Files.createTempDirectory(AggregationJobTest.class.getSimpleName()); + log.info("using work dir {}", workingDir); - SparkConf conf = new SparkConf(); + SparkConf conf = new SparkConf(); - conf.setAppName(AggregationJobTest.class.getSimpleName()); + conf.setAppName(AggregationJobTest.class.getSimpleName()); - conf.setMaster("local[*]"); - conf.set("spark.driver.host", "localhost"); - conf.set("hive.metastore.local", "true"); - conf.set("spark.ui.enabled", "false"); - conf.set("spark.sql.warehouse.dir", workingDir.toString()); - conf.set("hive.metastore.warehouse.dir", workingDir.resolve("warehouse").toString()); + conf.setMaster("local[*]"); + conf.set("spark.driver.host", "localhost"); + conf.set("hive.metastore.local", "true"); + conf.set("spark.ui.enabled", "false"); + conf.set("spark.sql.warehouse.dir", workingDir.toString()); + conf.set("hive.metastore.warehouse.dir", workingDir.resolve("warehouse").toString()); - encoder = Encoders.bean(MetadataRecord.class); - spark = SparkSession - .builder() - .appName(AggregationJobTest.class.getSimpleName()) - .config(conf) - .getOrCreate(); - } + encoder = Encoders.bean(MetadataRecord.class); + spark = SparkSession + .builder() + .appName(AggregationJobTest.class.getSimpleName()) + .config(conf) + .getOrCreate(); + } - @AfterAll - public static void afterAll() throws IOException { - FileUtils.deleteDirectory(workingDir.toFile()); - spark.stop(); - } + @AfterAll + public static void afterAll() throws IOException { + FileUtils.deleteDirectory(workingDir.toFile()); + spark.stop(); + } - @Test - @Order(1) - public void testGenerateNativeStoreSparkJobRefresh() throws Exception { + @Test + @Order(1) + public void testGenerateNativeStoreSparkJobRefresh() throws Exception { - MDStoreVersion mdStoreV1 = prepareVersion("/eu/dnetlib/dhp/collection/mdStoreVersion_1.json"); - FileUtils.forceMkdir(new File(mdStoreV1.getHdfsPath())); + MDStoreVersion mdStoreV1 = prepareVersion("/eu/dnetlib/dhp/collection/mdStoreVersion_1.json"); + FileUtils.forceMkdir(new File(mdStoreV1.getHdfsPath())); - IOUtils - .copy( - getClass().getResourceAsStream("/eu/dnetlib/dhp/collection/sequence_file"), - new FileOutputStream(mdStoreV1.getHdfsPath() + "/sequence_file")); + IOUtils + .copy( + getClass().getResourceAsStream("/eu/dnetlib/dhp/collection/sequence_file"), + new FileOutputStream(mdStoreV1.getHdfsPath() + "/sequence_file")); - GenerateNativeStoreSparkJob - .main( - new String[]{ - "-isSparkSessionManaged", Boolean.FALSE.toString(), - "-encoding", encoding, - "-dateOfCollection", dateOfCollection, - "-provenance", provenance, - "-xpath", xpath, - "-mdStoreVersion", OBJECT_MAPPER.writeValueAsString(mdStoreV1), - "-readMdStoreVersion", "", - "-workflowId", "abc" - }); + GenerateNativeStoreSparkJob + .main( + new String[] { + "-isSparkSessionManaged", Boolean.FALSE.toString(), + "-encoding", encoding, + "-dateOfCollection", dateOfCollection, + "-provenance", provenance, + "-xpath", xpath, + "-mdStoreVersion", OBJECT_MAPPER.writeValueAsString(mdStoreV1), + "-readMdStoreVersion", "", + "-workflowId", "abc" + }); - verify(mdStoreV1); - } + verify(mdStoreV1); + } - @Test - @Order(2) - public void testGenerateNativeStoreSparkJobIncremental() throws Exception { + @Test + @Order(2) + public void testGenerateNativeStoreSparkJobIncremental() throws Exception { - MDStoreVersion mdStoreV2 = prepareVersion("/eu/dnetlib/dhp/collection/mdStoreVersion_2.json"); - FileUtils.forceMkdir(new File(mdStoreV2.getHdfsPath())); + MDStoreVersion mdStoreV2 = prepareVersion("/eu/dnetlib/dhp/collection/mdStoreVersion_2.json"); + FileUtils.forceMkdir(new File(mdStoreV2.getHdfsPath())); - IOUtils - .copy( - getClass().getResourceAsStream("/eu/dnetlib/dhp/collection/sequence_file"), - new FileOutputStream(mdStoreV2.getHdfsPath() + "/sequence_file")); + IOUtils + .copy( + getClass().getResourceAsStream("/eu/dnetlib/dhp/collection/sequence_file"), + new FileOutputStream(mdStoreV2.getHdfsPath() + "/sequence_file")); - MDStoreVersion mdStoreV1 = prepareVersion("/eu/dnetlib/dhp/collection/mdStoreVersion_1.json"); + MDStoreVersion mdStoreV1 = prepareVersion("/eu/dnetlib/dhp/collection/mdStoreVersion_1.json"); - GenerateNativeStoreSparkJob - .main( - new String[]{ - "-isSparkSessionManaged", Boolean.FALSE.toString(), - "-encoding", encoding, - "-dateOfCollection", dateOfCollection, - "-provenance", provenance, - "-xpath", xpath, - "-mdStoreVersion", OBJECT_MAPPER.writeValueAsString(mdStoreV2), - "-readMdStoreVersion", OBJECT_MAPPER.writeValueAsString(mdStoreV1), - "-workflowId", "abc" - }); + GenerateNativeStoreSparkJob + .main( + new String[] { + "-isSparkSessionManaged", Boolean.FALSE.toString(), + "-encoding", encoding, + "-dateOfCollection", dateOfCollection, + "-provenance", provenance, + "-xpath", xpath, + "-mdStoreVersion", OBJECT_MAPPER.writeValueAsString(mdStoreV2), + "-readMdStoreVersion", OBJECT_MAPPER.writeValueAsString(mdStoreV1), + "-workflowId", "abc" + }); - verify(mdStoreV2); - } + verify(mdStoreV2); + } + @Test + @Order(3) + public void testTransformSparkJob() throws Exception { - @Test - @Order(3) - public void testTransformSparkJob() throws Exception { + MDStoreVersion mdStoreV2 = prepareVersion("/eu/dnetlib/dhp/collection/mdStoreVersion_2.json"); + MDStoreVersion mdStoreCleanedVersion = prepareVersion("/eu/dnetlib/dhp/collection/mdStoreCleanedVersion.json"); - MDStoreVersion mdStoreV2 = prepareVersion("/eu/dnetlib/dhp/collection/mdStoreVersion_2.json"); - MDStoreVersion mdStoreCleanedVersion = prepareVersion("/eu/dnetlib/dhp/collection/mdStoreCleanedVersion.json"); + TransformSparkJobNode.main(new String[] { + "-isSparkSessionManaged", Boolean.FALSE.toString(), + "-dateOfTransformation", dateOfCollection, + "-mdstoreInputVersion", OBJECT_MAPPER.writeValueAsString(mdStoreV2), + "-mdstoreOutputVersion", OBJECT_MAPPER.writeValueAsString(mdStoreCleanedVersion), + "-transformationPlugin", "XSLT_TRANSFORM", + "-isLookupUrl", "https://dev-openaire.d4science.org/is/services/isLookUp", + "-transformationRuleId", + "183dde52-a69b-4db9-a07e-1ef2be105294_VHJhbnNmb3JtYXRpb25SdWxlRFNSZXNvdXJjZXMvVHJhbnNmb3JtYXRpb25SdWxlRFNSZXNvdXJjZVR5cGU=" + }); - TransformSparkJobNode.main(new String[]{ - "-isSparkSessionManaged", Boolean.FALSE.toString(), - "-dateOfTransformation", dateOfCollection, - "-mdstoreInputVersion", OBJECT_MAPPER.writeValueAsString(mdStoreV2), - "-mdstoreOutputVersion", OBJECT_MAPPER.writeValueAsString(mdStoreCleanedVersion), - "-transformationPlugin", "XSLT_TRANSFORM", - "-isLookupUrl", "https://dev-openaire.d4science.org/is/services/isLookUp", - "-transformationRuleId", "183dde52-a69b-4db9-a07e-1ef2be105294_VHJhbnNmb3JtYXRpb25SdWxlRFNSZXNvdXJjZXMvVHJhbnNmb3JtYXRpb25SdWxlRFNSZXNvdXJjZVR5cGU="}); + } - } + protected void verify(MDStoreVersion mdStoreVersion) throws IOException { + Assertions.assertTrue(new File(mdStoreVersion.getHdfsPath()).exists()); - protected void verify(MDStoreVersion mdStoreVersion) throws IOException { - Assertions.assertTrue(new File(mdStoreVersion.getHdfsPath()).exists()); + final JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); + long seqFileSize = sc + .sequenceFile(mdStoreVersion.getHdfsPath() + "/sequence_file", IntWritable.class, Text.class) + .count(); - final JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); - long seqFileSize = sc - .sequenceFile(mdStoreVersion.getHdfsPath() + "/sequence_file", IntWritable.class, Text.class) - .count(); + final Dataset mdstore = spark.read().load(mdStoreVersion.getHdfsPath() + "/store").as(encoder); + long mdStoreSize = mdstore.count(); - final Dataset mdstore = spark.read().load(mdStoreVersion.getHdfsPath() + "/store").as(encoder); - long mdStoreSize = mdstore.count(); + long declaredSize = Long.parseLong(IOUtils.toString(new FileReader(mdStoreVersion.getHdfsPath() + "/size"))); - long declaredSize = Long.parseLong(IOUtils.toString(new FileReader(mdStoreVersion.getHdfsPath() + "/size"))); + Assertions.assertEquals(seqFileSize, declaredSize, "the size must be equal"); + Assertions.assertEquals(seqFileSize, mdStoreSize, "the size must be equal"); - Assertions.assertEquals(seqFileSize, declaredSize, "the size must be equal"); - Assertions.assertEquals(seqFileSize, mdStoreSize, "the size must be equal"); + long uniqueIds = mdstore + .map((MapFunction) MetadataRecord::getId, Encoders.STRING()) + .distinct() + .count(); - long uniqueIds = mdstore - .map((MapFunction) MetadataRecord::getId, Encoders.STRING()) - .distinct() - .count(); + Assertions.assertEquals(seqFileSize, uniqueIds, "the size must be equal"); + } - Assertions.assertEquals(seqFileSize, uniqueIds, "the size must be equal"); - } - - private MDStoreVersion prepareVersion(String filename) throws IOException { - MDStoreVersion mdstore = OBJECT_MAPPER - .readValue(IOUtils.toString(getClass().getResource(filename)), MDStoreVersion.class); - mdstore.setHdfsPath(String.format(mdstore.getHdfsPath(), workingDir.toString())); - return mdstore; - } + private MDStoreVersion prepareVersion(String filename) throws IOException { + MDStoreVersion mdstore = OBJECT_MAPPER + .readValue(IOUtils.toString(getClass().getResource(filename)), MDStoreVersion.class); + mdstore.setHdfsPath(String.format(mdstore.getHdfsPath(), workingDir.toString())); + return mdstore; + } } diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java index 9e46b5f95..d03c3acef 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java @@ -75,9 +75,6 @@ public class TransformationJobTest { spark.stop(); } - - - @Test @DisplayName("Test Transform Single XML using XSLTTransformator") public void testTransformSaxonHE() throws Exception { From ca4391aa1c5c03ecb0477fa287b77da97e3f9c8b Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 2 Feb 2021 12:44:04 +0100 Subject: [PATCH 085/445] minor changes --- .../transformation/TransformSparkJobNode.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java index 0a01faf1e..51f69de10 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java @@ -2,8 +2,7 @@ package eu.dnetlib.dhp.transformation; import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.*; -import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.saveDataset; -import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.writeTotalSizeOnHDFS; +import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.*; import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; import java.io.IOException; @@ -19,8 +18,6 @@ import org.apache.spark.util.LongAccumulator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.databind.ObjectMapper; - import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; import eu.dnetlib.dhp.aggregation.common.AggregationCounter; import eu.dnetlib.dhp.application.ArgumentApplicationParser; @@ -52,11 +49,14 @@ public class TransformSparkJobNode { final String mdstoreInputVersion = parser.get("mdstoreInputVersion"); final String mdstoreOutputVersion = parser.get("mdstoreOutputVersion"); - // TODO this variable will be used after implementing Messaging with DNet Aggregator - final ObjectMapper jsonMapper = new ObjectMapper(); - final MDStoreVersion nativeMdStoreVersion = jsonMapper.readValue(mdstoreInputVersion, MDStoreVersion.class); - final MDStoreVersion cleanedMdStoreVersion = jsonMapper.readValue(mdstoreOutputVersion, MDStoreVersion.class); + final MDStoreVersion nativeMdStoreVersion = MAPPER.readValue(mdstoreInputVersion, MDStoreVersion.class); + final String inputPath = nativeMdStoreVersion.getHdfsPath() + MDSTORE_DATA_PATH; + log.info("input path: {}", inputPath); + + final MDStoreVersion cleanedMdStoreVersion = MAPPER.readValue(mdstoreOutputVersion, MDStoreVersion.class); + final String outputPath = cleanedMdStoreVersion.getHdfsPath() + MDSTORE_DATA_PATH; + log.info("output path: {}", outputPath); final String isLookupUrl = parser.get("isLookupUrl"); log.info(String.format("isLookupUrl: %s", isLookupUrl)); @@ -74,9 +74,10 @@ public class TransformSparkJobNode { runWithSparkSession( conf, isSparkSessionManaged, - spark -> transformRecords( - parser.getObjectMap(), isLookupService, spark, nativeMdStoreVersion.getHdfsPath() + MDSTORE_DATA_PATH, - cleanedMdStoreVersion.getHdfsPath() + MDSTORE_DATA_PATH)); + spark -> { + transformRecords( + parser.getObjectMap(), isLookupService, spark, inputPath, outputPath); + }); } public static void transformRecords(final Map args, final ISLookUpService isLookUpService, From bde14b149a5e1d5eb249ef80db9d6d1a10d670a7 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 2 Feb 2021 12:49:29 +0100 Subject: [PATCH 086/445] fixed transformation target paths --- .../transformation/TransformSparkJobNode.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java index 51f69de10..e1830ed28 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java @@ -11,6 +11,7 @@ import java.util.Optional; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; +import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoder; import org.apache.spark.sql.Encoders; import org.apache.spark.sql.SparkSession; @@ -52,11 +53,11 @@ public class TransformSparkJobNode { final MDStoreVersion nativeMdStoreVersion = MAPPER.readValue(mdstoreInputVersion, MDStoreVersion.class); final String inputPath = nativeMdStoreVersion.getHdfsPath() + MDSTORE_DATA_PATH; - log.info("input path: {}", inputPath); + log.info("inputPath: {}", inputPath); final MDStoreVersion cleanedMdStoreVersion = MAPPER.readValue(mdstoreOutputVersion, MDStoreVersion.class); - final String outputPath = cleanedMdStoreVersion.getHdfsPath() + MDSTORE_DATA_PATH; - log.info("output path: {}", outputPath); + final String outputBasePath = cleanedMdStoreVersion.getHdfsPath(); + log.info("outputBasePath: {}", outputBasePath); final String isLookupUrl = parser.get("isLookupUrl"); log.info(String.format("isLookupUrl: %s", isLookupUrl)); @@ -76,12 +77,12 @@ public class TransformSparkJobNode { isSparkSessionManaged, spark -> { transformRecords( - parser.getObjectMap(), isLookupService, spark, inputPath, outputPath); + parser.getObjectMap(), isLookupService, spark, inputPath, outputBasePath); }); } public static void transformRecords(final Map args, final ISLookUpService isLookUpService, - final SparkSession spark, final String inputPath, final String outputPath) + final SparkSession spark, final String inputPath, final String outputBasePath) throws DnetTransformationException, IOException { final LongAccumulator totalItems = spark.sparkContext().longAccumulator(CONTENT_TOTALITEMS); @@ -90,22 +91,21 @@ public class TransformSparkJobNode { final AggregationCounter ct = new AggregationCounter(totalItems, errorItems, transformedItems); final Encoder encoder = Encoders.bean(MetadataRecord.class); - saveDataset( - spark + final Dataset mdstore = spark .read() .format("parquet") .load(inputPath) .as(encoder) .map( - TransformationFactory.getTransformationPlugin(args, ct, isLookUpService), - encoder), - outputPath + MDSTORE_DATA_PATH); + TransformationFactory.getTransformationPlugin(args, ct, isLookUpService), + encoder); + saveDataset(mdstore, outputBasePath + MDSTORE_DATA_PATH); log.info("Transformed item " + ct.getProcessedItems().count()); log.info("Total item " + ct.getTotalItems().count()); log.info("Transformation Error item " + ct.getErrorItems().count()); - writeTotalSizeOnHDFS(spark, ct.getProcessedItems().count(), outputPath + MDSTORE_SIZE_PATH); + writeTotalSizeOnHDFS(spark, mdstore.count(), outputBasePath + MDSTORE_SIZE_PATH); } } From ac46c247d2261c2dc2a1c5845d6355ca5088537f Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 2 Feb 2021 14:24:00 +0100 Subject: [PATCH 087/445] code formatting --- .../dhp/transformation/TransformSparkJobNode.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java index e1830ed28..e1b1b849c 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java @@ -92,13 +92,13 @@ public class TransformSparkJobNode { final Encoder encoder = Encoders.bean(MetadataRecord.class); final Dataset mdstore = spark - .read() - .format("parquet") - .load(inputPath) - .as(encoder) - .map( - TransformationFactory.getTransformationPlugin(args, ct, isLookUpService), - encoder); + .read() + .format("parquet") + .load(inputPath) + .as(encoder) + .map( + TransformationFactory.getTransformationPlugin(args, ct, isLookUpService), + encoder); saveDataset(mdstore, outputBasePath + MDSTORE_DATA_PATH); log.info("Transformed item " + ct.getProcessedItems().count()); From 53884d12c29d8ba746c4e6ebb68492b4212a1c45 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 2 Feb 2021 14:38:03 +0100 Subject: [PATCH 088/445] code formatting --- .../dhp/collection/oozie_app/workflow.xml | 19 +++---------------- .../dhp/transformation/oozie_app/workflow.xml | 11 ++++------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml index 9c213bee5..2b2cf9dce 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml @@ -4,7 +4,6 @@ apiDescription A json encoding of the API Description class - dataSourceInfo A json encoding of the Datasource Info @@ -13,50 +12,43 @@ identifierPath An xpath to retrieve the metadata identifier for the generation of DNet Identifier - metadataEncoding The type of the metadata XML/JSON - timestamp The timestamp of the collection date - workflowId The identifier of the workflow - mdStoreID The identifier of the mdStore - mdStoreManagerURI The URI of the MDStore Manager - collectionMode Should be REFRESH or INCREMENTAL - + ${jobTracker} ${nameNode} - + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] - ${wf:conf('collectionMode') eq 'REFRESH'} @@ -77,8 +69,6 @@ - - eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode @@ -129,7 +119,6 @@ - ${wf:conf('collectionMode') eq 'REFRESH'} @@ -182,8 +171,6 @@ - - eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode @@ -195,6 +182,6 @@ - +
\ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml index 43b270eaf..9e01936d4 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml @@ -29,11 +29,10 @@ isLookupUrl The IS lookUp service endopoint - - + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] @@ -51,11 +50,11 @@ --mdStoreID${mdStoreInputId} --mdStoreManagerURI${mdStoreManagerURI} -
+ @@ -69,7 +68,6 @@ --mdStoreID${mdStoreOutputId} --mdStoreManagerURI${mdStoreManagerURI} - @@ -173,8 +171,7 @@ - - - + + \ No newline at end of file From 0e8a4f9f1acbb03d1ec8c5cefcc6caff053cb532 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 3 Feb 2021 12:33:41 +0100 Subject: [PATCH 089/445] better logging, WIP: collectorWorker error reporting --- .../dhp/application/ApplicationUtils.java | 21 +++++ .../mdstore/MDStoreActionNode.java | 32 +++---- .../collection/plugin/CollectorPlugin.java | 3 + .../plugin/oai/OaiCollectorPlugin.java | 21 ++++- .../collection/plugin/oai/OaiIterator.java | 17 +++- .../plugin/oai/OaiIteratorFactory.java | 6 +- .../collection/worker/CollectorWorker.java | 87 +++++++++---------- .../worker/CollectorWorkerApplication.java | 20 +++-- .../worker/utils/CollectorPluginFactory.java | 2 +- .../worker/utils/HttpConnector.java | 84 +++++++----------- .../DnetCollectorWorkerApplicationTests.java | 2 +- 11 files changed, 159 insertions(+), 136 deletions(-) create mode 100644 dhp-common/src/main/java/eu/dnetlib/dhp/application/ApplicationUtils.java diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/application/ApplicationUtils.java b/dhp-common/src/main/java/eu/dnetlib/dhp/application/ApplicationUtils.java new file mode 100644 index 000000000..531c13af3 --- /dev/null +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/application/ApplicationUtils.java @@ -0,0 +1,21 @@ + +package eu.dnetlib.dhp.application; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.Properties; + +public class ApplicationUtils { + + public static void populateOOZIEEnv(final String paramName, String value) throws Exception { + File file = new File(System.getProperty("oozie.action.output.properties")); + Properties props = new Properties(); + + props.setProperty(paramName, value); + OutputStream os = new FileOutputStream(file); + props.store(os, ""); + os.close(); + } + +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java index 6cb0537b2..3e471cfc8 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java @@ -1,6 +1,9 @@ package eu.dnetlib.dhp.aggregation.mdstore; +import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.*; +import static eu.dnetlib.dhp.application.ApplicationUtils.*; + import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; @@ -16,11 +19,8 @@ import org.apache.hadoop.fs.Path; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.databind.ObjectMapper; - import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.collection.worker.CollectorWorker; import eu.dnetlib.dhp.common.rest.DNetRestClient; public class MDStoreActionNode { @@ -28,11 +28,8 @@ public class MDStoreActionNode { enum MDAction { NEW_VERSION, ROLLBACK, COMMIT, READ_LOCK, READ_UNLOCK - } - private static final ObjectMapper mapper = new ObjectMapper(); - public static String NEW_VERSION_URI = "%s/mdstore/%s/newVersion"; public static final String COMMIT_VERSION_URL = "%s/version/%s/commit/%s"; @@ -48,13 +45,13 @@ public class MDStoreActionNode { final ArgumentApplicationParser argumentParser = new ArgumentApplicationParser( IOUtils .toString( - CollectorWorker.class + MDStoreActionNode.class .getResourceAsStream( "/eu/dnetlib/dhp/collection/mdstore_action_parameters.json"))); argumentParser.parseArgument(args); final MDAction action = MDAction.valueOf(argumentParser.get("action")); - log.info("Curren action is {}", action); + log.info("Current action is {}", action); final String mdStoreManagerURI = argumentParser.get("mdStoreManagerURI"); log.info("mdStoreManagerURI is {}", mdStoreManagerURI); @@ -67,7 +64,7 @@ public class MDStoreActionNode { } final MDStoreVersion currentVersion = DNetRestClient .doGET(String.format(NEW_VERSION_URI, mdStoreManagerURI, mdStoreID), MDStoreVersion.class); - populateOOZIEEnv(MDSTOREVERSIONPARAM, mapper.writeValueAsString(currentVersion)); + populateOOZIEEnv(MDSTOREVERSIONPARAM, MAPPER.writeValueAsString(currentVersion)); break; } case COMMIT: { @@ -77,7 +74,7 @@ public class MDStoreActionNode { throw new IllegalArgumentException("missing or empty argument namenode"); } final String mdStoreVersion_params = argumentParser.get("mdStoreVersion"); - final MDStoreVersion mdStoreVersion = mapper.readValue(mdStoreVersion_params, MDStoreVersion.class); + final MDStoreVersion mdStoreVersion = MAPPER.readValue(mdStoreVersion_params, MDStoreVersion.class); if (StringUtils.isBlank(mdStoreVersion.getId())) { throw new IllegalArgumentException( @@ -110,7 +107,7 @@ public class MDStoreActionNode { } case ROLLBACK: { final String mdStoreVersion_params = argumentParser.get("mdStoreVersion"); - final MDStoreVersion mdStoreVersion = mapper.readValue(mdStoreVersion_params, MDStoreVersion.class); + final MDStoreVersion mdStoreVersion = MAPPER.readValue(mdStoreVersion_params, MDStoreVersion.class); if (StringUtils.isBlank(mdStoreVersion.getId())) { throw new IllegalArgumentException( @@ -127,12 +124,12 @@ public class MDStoreActionNode { } final MDStoreVersion currentVersion = DNetRestClient .doGET(String.format(READ_LOCK_URL, mdStoreManagerURI, mdStoreID), MDStoreVersion.class); - populateOOZIEEnv(MDSTOREREADLOCKPARAM, mapper.writeValueAsString(currentVersion)); + populateOOZIEEnv(MDSTOREREADLOCKPARAM, MAPPER.writeValueAsString(currentVersion)); break; } case READ_UNLOCK: { final String mdStoreVersion_params = argumentParser.get("readMDStoreId"); - final MDStoreVersion mdStoreVersion = mapper.readValue(mdStoreVersion_params, MDStoreVersion.class); + final MDStoreVersion mdStoreVersion = MAPPER.readValue(mdStoreVersion_params, MDStoreVersion.class); if (StringUtils.isBlank(mdStoreVersion.getId())) { throw new IllegalArgumentException( @@ -148,13 +145,4 @@ public class MDStoreActionNode { } - public static void populateOOZIEEnv(final String paramName, String value) throws Exception { - File file = new File(System.getProperty("oozie.action.output.properties")); - Properties props = new Properties(); - - props.setProperty(paramName, value); - OutputStream os = new FileOutputStream(file); - props.store(os, ""); - os.close(); - } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java index ba9bd662e..a0c546858 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java @@ -4,9 +4,12 @@ package eu.dnetlib.dhp.collection.plugin; import java.util.stream.Stream; import eu.dnetlib.dhp.collection.worker.CollectorException; +import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginErrorLogList; import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; public interface CollectorPlugin { Stream collect(ApiDescriptor api) throws CollectorException; + + CollectorPluginErrorLogList getCollectionErrors(); } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java index a5e261553..ea74919c5 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java @@ -9,12 +9,15 @@ import java.util.Spliterators; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import org.jetbrains.annotations.NotNull; + import com.google.common.base.Splitter; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; import eu.dnetlib.dhp.collection.worker.CollectorException; +import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginErrorLogList; import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; public class OaiCollectorPlugin implements CollectorPlugin { @@ -26,8 +29,19 @@ public class OaiCollectorPlugin implements CollectorPlugin { private OaiIteratorFactory oaiIteratorFactory; + private final CollectorPluginErrorLogList errorLogList = new CollectorPluginErrorLogList(); + @Override public Stream collect(final ApiDescriptor api) throws CollectorException { + try { + return doCollect(api); + } catch (CollectorException e) { + errorLogList.add(e.getMessage()); + throw e; + } + } + + private Stream doCollect(ApiDescriptor api) throws CollectorException { final String baseUrl = api.getBaseUrl(); final String mdFormat = api.getParams().get(FORMAT_PARAM); final String setParam = api.getParams().get(OAI_SET_PARAM); @@ -65,7 +79,7 @@ public class OaiCollectorPlugin implements CollectorPlugin { .stream() .map( set -> getOaiIteratorFactory() - .newIterator(baseUrl, mdFormat, set, fromDate, untilDate)) + .newIterator(baseUrl, mdFormat, set, fromDate, untilDate, errorLogList)) .iterator(); return StreamSupport @@ -79,4 +93,9 @@ public class OaiCollectorPlugin implements CollectorPlugin { } return oaiIteratorFactory; } + + @Override + public CollectorPluginErrorLogList getCollectionErrors() { + return errorLogList; + } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java index e54bae67d..2392dee6a 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java @@ -15,15 +15,17 @@ import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Node; import org.dom4j.io.SAXReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.collection.worker.CollectorException; +import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginErrorLogList; import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; import eu.dnetlib.dhp.collection.worker.utils.XmlCleaner; public class OaiIterator implements Iterator { - private static final Log log = LogFactory.getLog(OaiIterator.class); // NOPMD by marko on - // 11/24/08 5:02 PM + private static final Logger log = LoggerFactory.getLogger(OaiIterator.class); private final Queue queue = new PriorityBlockingQueue<>(); private final SAXReader reader = new SAXReader(); @@ -36,6 +38,7 @@ public class OaiIterator implements Iterator { private String token; private boolean started; private final HttpConnector httpConnector; + private CollectorPluginErrorLogList errorLogList; public OaiIterator( final String baseUrl, @@ -43,7 +46,8 @@ public class OaiIterator implements Iterator { final String set, final String fromDate, final String untilDate, - final HttpConnector httpConnector) { + final HttpConnector httpConnector, + final CollectorPluginErrorLogList errorLogList) { this.baseUrl = baseUrl; this.mdFormat = mdFormat; this.set = set; @@ -51,6 +55,7 @@ public class OaiIterator implements Iterator { this.untilDate = untilDate; this.started = false; this.httpConnector = httpConnector; + this.errorLogList = errorLogList; } private void verifyStarted() { @@ -139,7 +144,7 @@ public class OaiIterator implements Iterator { private String downloadPage(final String url) throws CollectorException { - final String xml = httpConnector.getInputSource(url); + final String xml = httpConnector.getInputSource(url, errorLogList); Document doc; try { doc = reader.read(new StringReader(xml)); @@ -174,4 +179,8 @@ public class OaiIterator implements Iterator { return doc.valueOf("//*[local-name()='resumptionToken']"); } + + public CollectorPluginErrorLogList getErrorLogList() { + return errorLogList; + } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java index 4a6ea7f67..eafd265d4 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java @@ -3,6 +3,7 @@ package eu.dnetlib.dhp.collection.plugin.oai; import java.util.Iterator; +import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginErrorLogList; import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; public class OaiIteratorFactory { @@ -14,8 +15,9 @@ public class OaiIteratorFactory { final String mdFormat, final String set, final String fromDate, - final String untilDate) { - return new OaiIterator(baseUrl, mdFormat, set, fromDate, untilDate, getHttpConnector()); + final String untilDate, + final CollectorPluginErrorLogList errorLogList) { + return new OaiIterator(baseUrl, mdFormat, set, fromDate, untilDate, getHttpConnector(), errorLogList); } private HttpConnector getHttpConnector() { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java index 3605bdfd6..7033cfd8e 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java @@ -15,6 +15,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; +import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginErrorLogList; import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginFactory; import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; @@ -22,69 +23,65 @@ public class CollectorWorker { private static final Logger log = LoggerFactory.getLogger(CollectorWorker.class); - private final CollectorPluginFactory collectorPluginFactory; - private final ApiDescriptor api; private final String hdfsuri; private final String hdfsPath; + private CollectorPlugin plugin; + public CollectorWorker( - final CollectorPluginFactory collectorPluginFactory, final ApiDescriptor api, final String hdfsuri, - final String hdfsPath) { - this.collectorPluginFactory = collectorPluginFactory; + final String hdfsPath) throws CollectorException { this.api = api; this.hdfsuri = hdfsuri; this.hdfsPath = hdfsPath; - + this.plugin = CollectorPluginFactory.getPluginByProtocol(api.getProtocol()); } - public void collect() throws CollectorException { - try { - final CollectorPlugin plugin = collectorPluginFactory.getPluginByProtocol(api.getProtocol()); + public CollectorPluginErrorLogList collect() throws IOException, CollectorException { - // ====== Init HDFS File System Object - Configuration conf = new Configuration(); - // Set FileSystem URI - conf.set("fs.defaultFS", hdfsuri); - // Because of Maven - conf.set("fs.hdfs.impl", org.apache.hadoop.hdfs.DistributedFileSystem.class.getName()); - conf.set("fs.file.impl", org.apache.hadoop.fs.LocalFileSystem.class.getName()); + // ====== Init HDFS File System Object + Configuration conf = new Configuration(); + // Set FileSystem URI + conf.set("fs.defaultFS", hdfsuri); + // Because of Maven + conf.set("fs.hdfs.impl", org.apache.hadoop.hdfs.DistributedFileSystem.class.getName()); + conf.set("fs.file.impl", org.apache.hadoop.fs.LocalFileSystem.class.getName()); - System.setProperty("hadoop.home.dir", "/"); - // Get the filesystem - HDFS - FileSystem.get(URI.create(hdfsuri), conf); - Path hdfswritepath = new Path(hdfsPath); + System.setProperty("hadoop.home.dir", "/"); + // Get the filesystem - HDFS - log.info("Created path " + hdfswritepath.toString()); + FileSystem.get(URI.create(hdfsuri), conf); + Path hdfswritepath = new Path(hdfsPath); - final AtomicInteger counter = new AtomicInteger(0); - try (SequenceFile.Writer writer = SequenceFile - .createWriter( - conf, - SequenceFile.Writer.file(hdfswritepath), - SequenceFile.Writer.keyClass(IntWritable.class), - SequenceFile.Writer.valueClass(Text.class))) { - final IntWritable key = new IntWritable(counter.get()); - final Text value = new Text(); - plugin - .collect(api) - .forEach( - content -> { - key.set(counter.getAndIncrement()); - value.set(content); - try { - writer.append(key, value); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - } - } catch (Throwable e) { - throw new CollectorException("Error on collecting ", e); + log.info("Created path " + hdfswritepath.toString()); + + final AtomicInteger counter = new AtomicInteger(0); + try (SequenceFile.Writer writer = SequenceFile + .createWriter( + conf, + SequenceFile.Writer.file(hdfswritepath), + SequenceFile.Writer.keyClass(IntWritable.class), + SequenceFile.Writer.valueClass(Text.class))) { + final IntWritable key = new IntWritable(counter.get()); + final Text value = new Text(); + plugin + .collect(api) + .forEach( + content -> { + key.set(counter.getAndIncrement()); + value.set(content); + try { + writer.append(key, value); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } finally { + return plugin.getCollectionErrors(); } } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java index da5b197d6..1d99689db 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java @@ -2,6 +2,8 @@ package eu.dnetlib.dhp.collection.worker; import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.*; +import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.*; +import static eu.dnetlib.dhp.application.ApplicationUtils.*; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; @@ -10,7 +12,9 @@ import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; +import eu.dnetlib.dhp.aggregation.common.AggregationUtility; import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginErrorLogList; import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginFactory; import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; @@ -25,8 +29,6 @@ public class CollectorWorkerApplication { private static final Logger log = LoggerFactory.getLogger(CollectorWorkerApplication.class); - private static final CollectorPluginFactory collectorPluginFactory = new CollectorPluginFactory(); - /** * @param args */ @@ -49,14 +51,16 @@ public class CollectorWorkerApplication { final String mdStoreVersion = argumentParser.get("mdStoreVersion"); log.info("mdStoreVersion is {}", mdStoreVersion); - final ObjectMapper jsonMapper = new ObjectMapper(); + final MDStoreVersion currentVersion = MAPPER.readValue(mdStoreVersion, MDStoreVersion.class); + final String hdfsPath = currentVersion.getHdfsPath() + SEQUENCE_FILE_NAME; + log.info("hdfs path is {}", hdfsPath); - final MDStoreVersion currentVersion = jsonMapper.readValue(mdStoreVersion, MDStoreVersion.class); + final ApiDescriptor api = MAPPER.readValue(apiDescriptor, ApiDescriptor.class); - final ApiDescriptor api = jsonMapper.readValue(apiDescriptor, ApiDescriptor.class); - final CollectorWorker worker = new CollectorWorker(collectorPluginFactory, api, hdfsuri, - currentVersion.getHdfsPath() + SEQUENCE_FILE_NAME); - worker.collect(); + final CollectorWorker worker = new CollectorWorker(api, hdfsuri, hdfsPath); + CollectorPluginErrorLogList errors = worker.collect(); + + populateOOZIEEnv("collectorErrors", errors.toString()); } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginFactory.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginFactory.java index 6b070b191..7cbcd9b5c 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginFactory.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginFactory.java @@ -7,7 +7,7 @@ import eu.dnetlib.dhp.collection.worker.CollectorException; public class CollectorPluginFactory { - public CollectorPlugin getPluginByProtocol(final String protocol) throws CollectorException { + public static CollectorPlugin getPluginByProtocol(final String protocol) throws CollectorException { if (protocol == null) throw new CollectorException("protocol cannot be null"); switch (protocol.toLowerCase().trim()) { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector.java index ff3c18aba..fc45b4814 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector.java @@ -16,14 +16,14 @@ import javax.net.ssl.X509TrustManager; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.math.NumberUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.collection.worker.CollectorException; public class HttpConnector { - private static final Log log = LogFactory.getLog(HttpConnector.class); + private static final Logger log = LoggerFactory.getLogger(HttpConnector.class); private int maxNumberOfRetry = 6; private int defaultDelay = 120; // seconds @@ -45,7 +45,20 @@ public class HttpConnector { * @throws CollectorException when retrying more than maxNumberOfRetry times */ public String getInputSource(final String requestUrl) throws CollectorException { - return attemptDownlaodAsString(requestUrl, 1, new CollectorPluginErrorLogList()); + return attemptDownloadAsString(requestUrl, 1, new CollectorPluginErrorLogList()); + } + + /** + * Given the URL returns the content via HTTP GET + * + * @param requestUrl the URL + * @param errorLogList the list of errors + * @return the content of the downloaded resource + * @throws CollectorException when retrying more than maxNumberOfRetry times + */ + public String getInputSource(final String requestUrl, CollectorPluginErrorLogList errorLogList) + throws CollectorException { + return attemptDownloadAsString(requestUrl, 1, errorLogList); } /** @@ -59,18 +72,20 @@ public class HttpConnector { return attemptDownload(requestUrl, 1, new CollectorPluginErrorLogList()); } - private String attemptDownlaodAsString( + private String attemptDownloadAsString( final String requestUrl, final int retryNumber, final CollectorPluginErrorLogList errorList) throws CollectorException { + + log.info("requesting URL [{}]", requestUrl); try { final InputStream s = attemptDownload(requestUrl, 1, new CollectorPluginErrorLogList()); try { return IOUtils.toString(s); } catch (final IOException e) { - log.error("error while retrieving from http-connection occured: " + requestUrl, e); + log.error("error while retrieving from http-connection occurred: {}", requestUrl, e); Thread.sleep(defaultDelay * 1000); errorList.add(e.getMessage()); - return attemptDownlaodAsString(requestUrl, retryNumber + 1, errorList); + return attemptDownloadAsString(requestUrl, retryNumber + 1, errorList); } finally { IOUtils.closeQuietly(s); } @@ -87,7 +102,7 @@ public class HttpConnector { throw new CollectorException("Max number of retries exceeded. Cause: \n " + errorList); } - log.debug("Downloading " + requestUrl + " - try: " + retryNumber); + log.debug("requesting URL [{}], try {}", requestUrl, retryNumber); try { InputStream input = null; @@ -103,7 +118,7 @@ public class HttpConnector { final int retryAfter = obtainRetryAfter(urlConn.getHeaderFields()); if (retryAfter > 0 && urlConn.getResponseCode() == HttpURLConnection.HTTP_UNAVAILABLE) { - log.warn("waiting and repeating request after " + retryAfter + " sec."); + log.warn("waiting and repeating request after {} sec.", retryAfter); Thread.sleep(retryAfter * 1000); errorList.add("503 Service Unavailable"); urlConn.disconnect(); @@ -111,7 +126,7 @@ public class HttpConnector { } else if (urlConn.getResponseCode() == HttpURLConnection.HTTP_MOVED_PERM || urlConn.getResponseCode() == HttpURLConnection.HTTP_MOVED_TEMP) { final String newUrl = obtainNewLocation(urlConn.getHeaderFields()); - log.debug("The requested url has been moved to " + newUrl); + log.debug("The requested url has been moved to {}", newUrl); errorList .add( String @@ -121,15 +136,11 @@ public class HttpConnector { urlConn.disconnect(); return attemptDownload(newUrl, retryNumber + 1, errorList); } else if (urlConn.getResponseCode() != HttpURLConnection.HTTP_OK) { - log - .error( - String - .format( - "HTTP error: %s %s", urlConn.getResponseCode(), urlConn.getResponseMessage())); + final String msg = String + .format("HTTP error: %s %s", urlConn.getResponseCode(), urlConn.getResponseMessage()); + log.error(msg); Thread.sleep(defaultDelay * 1000); - errorList - .add( - String.format("%s %s", urlConn.getResponseCode(), urlConn.getResponseMessage())); + errorList.add(msg); urlConn.disconnect(); return attemptDownload(requestUrl, retryNumber + 1, errorList); } else { @@ -138,7 +149,7 @@ public class HttpConnector { return input; } } catch (final IOException e) { - log.error("error while retrieving from http-connection occured: " + requestUrl, e); + log.error("error while retrieving from http-connection occurred: {}", requestUrl, e); Thread.sleep(defaultDelay * 1000); errorList.add(e.getMessage()); return attemptDownload(requestUrl, retryNumber + 1, errorList); @@ -149,12 +160,12 @@ public class HttpConnector { } private void logHeaderFields(final HttpURLConnection urlConn) throws IOException { - log.debug("StatusCode: " + urlConn.getResponseMessage()); + log.debug("StatusCode: {}", urlConn.getResponseMessage()); for (final Map.Entry> e : urlConn.getHeaderFields().entrySet()) { if (e.getKey() != null) { for (final String v : e.getValue()) { - log.debug(" key: " + e.getKey() + " - value: " + v); + log.debug(" key: {} value: {}", e.getKey(), v); } } } @@ -183,37 +194,6 @@ public class HttpConnector { "The requested url has been MOVED, but 'location' param is MISSING"); } - /** - * register for https scheme; this is a workaround and not intended for the use in trusted environments - */ - public void initTrustManager() { - final X509TrustManager tm = new X509TrustManager() { - - @Override - public void checkClientTrusted(final X509Certificate[] xcs, final String string) { - } - - @Override - public void checkServerTrusted(final X509Certificate[] xcs, final String string) { - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return null; - } - }; - try { - final SSLContext ctx = SSLContext.getInstance("TLS"); - ctx.init(null, new TrustManager[] { - tm - }, null); - HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory()); - } catch (final GeneralSecurityException e) { - log.fatal(e); - throw new IllegalStateException(e); - } - } - public int getMaxNumberOfRetry() { return maxNumberOfRetry; } diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/DnetCollectorWorkerApplicationTests.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/DnetCollectorWorkerApplicationTests.java index 9abfbacac..10964096c 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/DnetCollectorWorkerApplicationTests.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/DnetCollectorWorkerApplicationTests.java @@ -40,7 +40,7 @@ public class DnetCollectorWorkerApplicationTests { public void testFeeding(@TempDir Path testDir) throws Exception { System.out.println(testDir.toString()); - CollectorWorker worker = new CollectorWorker(new CollectorPluginFactory(), getApi(), + CollectorWorker worker = new CollectorWorker(getApi(), "file://" + testDir.toString() + "/file.seq", testDir.toString() + "/file.seq"); worker.collect(); From c286d28ad2c313ba2242145b788bcaf3171712c6 Mon Sep 17 00:00:00 2001 From: "michele.artini" Date: Wed, 3 Feb 2021 16:07:49 +0100 Subject: [PATCH 090/445] logs --- .../data/mdstore/manager/common/model/MDStore.java | 12 +++++++++--- .../manager/common/model/MDStoreCurrentVersion.java | 8 ++++++-- .../manager/common/model/MDStoreVersion.java | 12 +++++++++--- .../manager/common/model/MDStoreWithInfo.java | 13 ++++++++++--- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStore.java b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStore.java index db200cd6a..59fe941ed 100644 --- a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStore.java +++ b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStore.java @@ -157,7 +157,9 @@ public class MDStore implements Serializable { @Override public String toString() { return String - .format("MDStore [id=%s, format=%s, layout=%s, interpretation=%s, datasourceName=%s, datasourceId=%s, apiId=%s, hdfsPath=%s, creationDate=%s]", id, format, layout, interpretation, datasourceName, datasourceId, apiId, hdfsPath, creationDate); + .format( + "MDStore [id=%s, format=%s, layout=%s, interpretation=%s, datasourceName=%s, datasourceId=%s, apiId=%s, hdfsPath=%s, creationDate=%s]", + id, format, layout, interpretation, datasourceName, datasourceId, apiId, hdfsPath, creationDate); } @Override @@ -167,8 +169,12 @@ public class MDStore implements Serializable { @Override public boolean equals(final Object obj) { - if (this == obj) { return true; } - if (!(obj instanceof MDStore)) { return false; } + if (this == obj) { + return true; + } + if (!(obj instanceof MDStore)) { + return false; + } final MDStore other = (MDStore) obj; return Objects.equals(id, other.id); } diff --git a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreCurrentVersion.java b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreCurrentVersion.java index e25e7dc2a..d808e2de7 100644 --- a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreCurrentVersion.java +++ b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreCurrentVersion.java @@ -62,8 +62,12 @@ public class MDStoreCurrentVersion implements Serializable { @Override public boolean equals(final Object obj) { - if (this == obj) { return true; } - if (!(obj instanceof MDStoreCurrentVersion)) { return false; } + if (this == obj) { + return true; + } + if (!(obj instanceof MDStoreCurrentVersion)) { + return false; + } final MDStoreCurrentVersion other = (MDStoreCurrentVersion) obj; return Objects.equals(currentVersion, other.currentVersion) && Objects.equals(mdstore, other.mdstore); } diff --git a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreVersion.java b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreVersion.java index 26c34fcad..38f8f275e 100644 --- a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreVersion.java +++ b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreVersion.java @@ -116,7 +116,9 @@ public class MDStoreVersion implements Serializable { @Override public String toString() { return String - .format("MDStoreVersion [id=%s, mdstore=%s, writing=%s, readCount=%s, lastUpdate=%s, size=%s, hdfsPath=%s]", id, mdstore, writing, readCount, lastUpdate, size, hdfsPath); + .format( + "MDStoreVersion [id=%s, mdstore=%s, writing=%s, readCount=%s, lastUpdate=%s, size=%s, hdfsPath=%s]", id, + mdstore, writing, readCount, lastUpdate, size, hdfsPath); } @Override @@ -126,8 +128,12 @@ public class MDStoreVersion implements Serializable { @Override public boolean equals(final Object obj) { - if (this == obj) { return true; } - if (!(obj instanceof MDStoreVersion)) { return false; } + if (this == obj) { + return true; + } + if (!(obj instanceof MDStoreVersion)) { + return false; + } final MDStoreVersion other = (MDStoreVersion) obj; return Objects.equals(id, other.id); } diff --git a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreWithInfo.java b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreWithInfo.java index e34e4c000..510c65092 100644 --- a/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreWithInfo.java +++ b/dhp-common/src/main/java/eu/dnetlib/data/mdstore/manager/common/model/MDStoreWithInfo.java @@ -168,7 +168,10 @@ public class MDStoreWithInfo implements Serializable { @Override public String toString() { return String - .format("MDStoreWithInfo [id=%s, format=%s, layout=%s, interpretation=%s, datasourceName=%s, datasourceId=%s, apiId=%s, currentVersion=%s, creationDate=%s, lastUpdate=%s, size=%s, numberOfVersions=%s, hdfsPath=%s]", id, format, layout, interpretation, datasourceName, datasourceId, apiId, currentVersion, creationDate, lastUpdate, size, numberOfVersions, hdfsPath); + .format( + "MDStoreWithInfo [id=%s, format=%s, layout=%s, interpretation=%s, datasourceName=%s, datasourceId=%s, apiId=%s, currentVersion=%s, creationDate=%s, lastUpdate=%s, size=%s, numberOfVersions=%s, hdfsPath=%s]", + id, format, layout, interpretation, datasourceName, datasourceId, apiId, currentVersion, creationDate, + lastUpdate, size, numberOfVersions, hdfsPath); } @Override @@ -178,8 +181,12 @@ public class MDStoreWithInfo implements Serializable { @Override public boolean equals(final Object obj) { - if (this == obj) { return true; } - if (!(obj instanceof MDStoreWithInfo)) { return false; } + if (this == obj) { + return true; + } + if (!(obj instanceof MDStoreWithInfo)) { + return false; + } final MDStoreWithInfo other = (MDStoreWithInfo) obj; return Objects.equals(id, other.id); } From 820d729e99011887ae1419c4eb4c5fe4ed0b5d6b Mon Sep 17 00:00:00 2001 From: "michele.artini" Date: Wed, 3 Feb 2021 16:20:34 +0100 Subject: [PATCH 091/445] recover of Message and MessageType class --- .../main/java/eu/dnetlib/message/Message.java | 58 +++++++++++++++++++ .../java/eu/dnetlib/message/MessageType.java | 6 ++ 2 files changed, 64 insertions(+) create mode 100644 dhp-common/src/main/java/eu/dnetlib/message/Message.java create mode 100644 dhp-common/src/main/java/eu/dnetlib/message/MessageType.java diff --git a/dhp-common/src/main/java/eu/dnetlib/message/Message.java b/dhp-common/src/main/java/eu/dnetlib/message/Message.java new file mode 100644 index 000000000..8932e02f3 --- /dev/null +++ b/dhp-common/src/main/java/eu/dnetlib/message/Message.java @@ -0,0 +1,58 @@ + +package eu.dnetlib.message; + +import java.util.Map; + +public class Message { + + private String workflowId; + + private String jobName; + + private MessageType type; + + private Map body; + + public Message() { + } + + public Message(final String workflowId, final String jobName, final MessageType type, + final Map body) { + this.workflowId = workflowId; + this.jobName = jobName; + this.type = type; + this.body = body; + } + + public String getWorkflowId() { + return workflowId; + } + + public void setWorkflowId(final String workflowId) { + this.workflowId = workflowId; + } + + public String getJobName() { + return jobName; + } + + public void setJobName(final String jobName) { + this.jobName = jobName; + } + + public MessageType getType() { + return type; + } + + public void setType(final MessageType type) { + this.type = type; + } + + public Map getBody() { + return body; + } + + public void setBody(final Map body) { + this.body = body; + } +} diff --git a/dhp-common/src/main/java/eu/dnetlib/message/MessageType.java b/dhp-common/src/main/java/eu/dnetlib/message/MessageType.java new file mode 100644 index 000000000..72cbda252 --- /dev/null +++ b/dhp-common/src/main/java/eu/dnetlib/message/MessageType.java @@ -0,0 +1,6 @@ + +package eu.dnetlib.message; + +public enum MessageType { + ONGOING, REPORT +} From 1b9731632ba7cdc65780eba71aaebb5499faed51 Mon Sep 17 00:00:00 2001 From: "michele.artini" Date: Wed, 3 Feb 2021 16:42:36 +0100 Subject: [PATCH 092/445] Message Sender --- .../main/java/eu/dnetlib/message/Message.java | 13 ++++++- .../eu/dnetlib/message/MessageSender.java | 35 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 dhp-common/src/main/java/eu/dnetlib/message/MessageSender.java diff --git a/dhp-common/src/main/java/eu/dnetlib/message/Message.java b/dhp-common/src/main/java/eu/dnetlib/message/Message.java index 8932e02f3..2de8ead42 100644 --- a/dhp-common/src/main/java/eu/dnetlib/message/Message.java +++ b/dhp-common/src/main/java/eu/dnetlib/message/Message.java @@ -1,9 +1,15 @@ package eu.dnetlib.message; +import java.io.Serializable; import java.util.Map; -public class Message { +public class Message implements Serializable { + + /** + * + */ + private static final long serialVersionUID = 401753881204524893L; private String workflowId; @@ -55,4 +61,9 @@ public class Message { public void setBody(final Map body) { this.body = body; } + + @Override + public String toString() { + return String.format("Message [workflowId=%s, jobName=%s, type=%s, body=%s]", workflowId, jobName, type, body); + } } diff --git a/dhp-common/src/main/java/eu/dnetlib/message/MessageSender.java b/dhp-common/src/main/java/eu/dnetlib/message/MessageSender.java new file mode 100644 index 000000000..020d6087f --- /dev/null +++ b/dhp-common/src/main/java/eu/dnetlib/message/MessageSender.java @@ -0,0 +1,35 @@ + +package eu.dnetlib.message; + +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.entity.SerializableEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MessageSender { + + private static final Logger log = LoggerFactory.getLogger(MessageSender.class); + + private final String dnetMessageEndpoint; + + public MessageSender(final String dnetMessageEndpoint) { + this.dnetMessageEndpoint = dnetMessageEndpoint; + } + + public void sendMessage(final Message message) { + final HttpPut req = new HttpPut(dnetMessageEndpoint); + req.setEntity(new SerializableEntity(message)); + + try (final CloseableHttpClient client = HttpClients.createDefault(); + final CloseableHttpResponse response = client.execute(req)) { + log.debug("Sent Message to " + dnetMessageEndpoint); + log.debug("MESSAGE:" + message); + } catch (final Throwable e) { + log.error("Error sending message to " + dnetMessageEndpoint + ", message content: " + message, e); + } + } + +} From e04045089f5bfc793f15cd27188b04761a61f37a Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 3 Feb 2021 17:58:22 +0100 Subject: [PATCH 093/445] better logging, WIP: collectorWorker error reporting --- .../dhp/collection/plugin/oai/OaiIterator.java | 4 ++-- .../worker/CollectorWorkerApplication.java | 13 ++++++++----- .../dnetlib/dhp/collection/oozie_app/workflow.xml | 3 +-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java index 2392dee6a..df0722905 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java @@ -149,14 +149,14 @@ public class OaiIterator implements Iterator { try { doc = reader.read(new StringReader(xml)); } catch (final DocumentException e) { - log.warn("Error parsing xml, I try to clean it: " + xml, e); + log.warn("Error parsing xml, I try to clean it. {}", e.getMessage()); final String cleaned = XmlCleaner.cleanAllEntities(xml); try { doc = reader.read(new StringReader(cleaned)); } catch (final DocumentException e1) { final String resumptionToken = extractResumptionToken(xml); if (resumptionToken == null) { - throw new CollectorException("Error parsing cleaned document:" + cleaned, e1); + throw new CollectorException("Error parsing cleaned document:\n" + cleaned, e1); } return resumptionToken; } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java index 1d99689db..d89bcee54 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java @@ -19,16 +19,19 @@ import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginFactory; import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; /** - * DnetCollectortWorkerApplication is the main class responsible to start the Dnet Collection into HDFS. This module - * will be executed on the hadoop cluster and taking in input some parameters that tells it which is the right collector - * plugin to use and where store the data into HDFS path + * CollectorWorkerApplication is the main class responsible to start the metadata collection process, storing the outcomes + * into HDFS. This application will be executed on the hadoop cluster, where invoked in the context of the metadata collection + * oozie workflow, it will receive all the input parameters necessary to instantiate the specific collection plugin and the + * relative specific configurations * - * @author Sandro La Bruzzo + * @author Sandro La Bruzzo, Claudio Atzori */ public class CollectorWorkerApplication { private static final Logger log = LoggerFactory.getLogger(CollectorWorkerApplication.class); + public static final String COLLECTOR_WORKER_ERRORS = "collectorWorker-errors"; + /** * @param args */ @@ -60,7 +63,7 @@ public class CollectorWorkerApplication { final CollectorWorker worker = new CollectorWorker(api, hdfsuri, hdfsPath); CollectorPluginErrorLogList errors = worker.collect(); - populateOOZIEEnv("collectorErrors", errors.toString()); + populateOOZIEEnv(COLLECTOR_WORKER_ERRORS, errors.toString()); } diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml index 2b2cf9dce..595613a2e 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml @@ -87,6 +87,7 @@ --apidescriptor${apiDescription} --namenode${nameNode} --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} + @@ -133,7 +134,6 @@ --actionREAD_UNLOCK --mdStoreManagerURI${mdStoreManagerURI} --readMDStoreId${wf:actionData('BeginRead')['mdStoreReadLockVersion']} - @@ -165,7 +165,6 @@ --actionREAD_UNLOCK --mdStoreManagerURI${mdStoreManagerURI} --readMDStoreId${wf:actionData('BeginRead')['mdStoreReadLockVersion']} - From 26d2eb946fc0922cb0596053df3ff77b6143a0c4 Mon Sep 17 00:00:00 2001 From: "michele.artini" Date: Thu, 4 Feb 2021 09:45:46 +0100 Subject: [PATCH 094/445] messages sender --- .../eu/dnetlib/{ => dhp}/message/Message.java | 17 +++--------- .../{ => dhp}/message/MessageSender.java | 26 +++++++++++++++++-- .../java/eu/dnetlib/message/MessageType.java | 6 ----- 3 files changed, 27 insertions(+), 22 deletions(-) rename dhp-common/src/main/java/eu/dnetlib/{ => dhp}/message/Message.java (68%) rename dhp-common/src/main/java/eu/dnetlib/{ => dhp}/message/MessageSender.java (59%) delete mode 100644 dhp-common/src/main/java/eu/dnetlib/message/MessageType.java diff --git a/dhp-common/src/main/java/eu/dnetlib/message/Message.java b/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java similarity index 68% rename from dhp-common/src/main/java/eu/dnetlib/message/Message.java rename to dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java index 2de8ead42..57844d490 100644 --- a/dhp-common/src/main/java/eu/dnetlib/message/Message.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java @@ -1,5 +1,5 @@ -package eu.dnetlib.message; +package eu.dnetlib.dhp.message; import java.io.Serializable; import java.util.Map; @@ -15,18 +15,15 @@ public class Message implements Serializable { private String jobName; - private MessageType type; - private Map body; public Message() { } - public Message(final String workflowId, final String jobName, final MessageType type, + public Message(final String workflowId, final String jobName, final Map body) { this.workflowId = workflowId; this.jobName = jobName; - this.type = type; this.body = body; } @@ -46,14 +43,6 @@ public class Message implements Serializable { this.jobName = jobName; } - public MessageType getType() { - return type; - } - - public void setType(final MessageType type) { - this.type = type; - } - public Map getBody() { return body; } @@ -64,6 +53,6 @@ public class Message implements Serializable { @Override public String toString() { - return String.format("Message [workflowId=%s, jobName=%s, type=%s, body=%s]", workflowId, jobName, type, body); + return String.format("Message [workflowId=%s, jobName=%s, body=%s]", workflowId, jobName, body); } } diff --git a/dhp-common/src/main/java/eu/dnetlib/message/MessageSender.java b/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageSender.java similarity index 59% rename from dhp-common/src/main/java/eu/dnetlib/message/MessageSender.java rename to dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageSender.java index 020d6087f..70eb594f8 100644 --- a/dhp-common/src/main/java/eu/dnetlib/message/MessageSender.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageSender.java @@ -1,6 +1,7 @@ -package eu.dnetlib.message; +package eu.dnetlib.dhp.message; +import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.SerializableEntity; @@ -13,6 +14,12 @@ public class MessageSender { private static final Logger log = LoggerFactory.getLogger(MessageSender.class); + private static final int SOCKET_TIMEOUT_MS = 2000; + + private static final int CONNECTION_REQUEST_TIMEOUT_MS = 2000; + + private static final int CONNTECTION_TIMEOUT_MS = 2000; + private final String dnetMessageEndpoint; public MessageSender(final String dnetMessageEndpoint) { @@ -20,10 +27,25 @@ public class MessageSender { } public void sendMessage(final Message message) { + new Thread(() -> _sendMessage(message)).start(); + } + + private void _sendMessage(final Message message) { final HttpPut req = new HttpPut(dnetMessageEndpoint); req.setEntity(new SerializableEntity(message)); - try (final CloseableHttpClient client = HttpClients.createDefault(); + final RequestConfig requestConfig = RequestConfig + .custom() + .setConnectTimeout(CONNTECTION_TIMEOUT_MS) + .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT_MS) + .setSocketTimeout(SOCKET_TIMEOUT_MS) + .build(); + ; + + try (final CloseableHttpClient client = HttpClients + .custom() + .setDefaultRequestConfig(requestConfig) + .build(); final CloseableHttpResponse response = client.execute(req)) { log.debug("Sent Message to " + dnetMessageEndpoint); log.debug("MESSAGE:" + message); diff --git a/dhp-common/src/main/java/eu/dnetlib/message/MessageType.java b/dhp-common/src/main/java/eu/dnetlib/message/MessageType.java deleted file mode 100644 index 72cbda252..000000000 --- a/dhp-common/src/main/java/eu/dnetlib/message/MessageType.java +++ /dev/null @@ -1,6 +0,0 @@ - -package eu.dnetlib.message; - -public enum MessageType { - ONGOING, REPORT -} From 69c253710be8f088d7d8ce4b262db2fdbf79594d Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Thu, 4 Feb 2021 10:30:49 +0100 Subject: [PATCH 095/445] fixed test --- .../aggregation/AbstractVocabularyTest.java | 52 +++++++++ .../dhp/aggregation/AggregationJobTest.java | 105 ++++++++++++------ .../transformation/TransformationJobTest.java | 88 +++++---------- 3 files changed, 148 insertions(+), 97 deletions(-) create mode 100644 dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AbstractVocabularyTest.java diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AbstractVocabularyTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AbstractVocabularyTest.java new file mode 100644 index 000000000..84878bd1b --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AbstractVocabularyTest.java @@ -0,0 +1,52 @@ +package eu.dnetlib.dhp.aggregation; + +import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; +import eu.dnetlib.dhp.transformation.TransformationFactory; +import eu.dnetlib.dhp.transformation.TransformationJobTest; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import org.apache.commons.io.IOUtils; +import org.mockito.Mock; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import static org.mockito.Mockito.lenient; + +public abstract class AbstractVocabularyTest { + + @Mock + protected ISLookUpService isLookUpService; + + protected VocabularyGroup vocabularies; + + + + public void setUpVocabulary() throws ISLookUpException, IOException { + lenient().when(isLookUpService.quickSearchProfile(VocabularyGroup.VOCABULARIES_XQUERY)).thenReturn(vocs()); + + lenient() + .when(isLookUpService.quickSearchProfile(VocabularyGroup.VOCABULARY_SYNONYMS_XQUERY)) + .thenReturn(synonyms()); + vocabularies = VocabularyGroup.loadVocsFromIS(isLookUpService); + } + + private static List vocs() throws IOException { + return IOUtils + .readLines(TransformationJobTest.class.getResourceAsStream("/eu/dnetlib/dhp/transform/terms.txt")); + } + + private static List synonyms() throws IOException { + return IOUtils + .readLines(TransformationJobTest.class.getResourceAsStream("/eu/dnetlib/dhp/transform/synonyms.txt")); + } + + protected void mockupTrasformationRule(final String trule, final String path) throws Exception { + final String trValue = IOUtils.toString(this.getClass().getResourceAsStream(path)); + + lenient() + .when(isLookUpService.quickSearchProfile(String.format(TransformationFactory.TRULE_XQUERY, trule))) + .thenReturn(Collections.singletonList(trValue)); + } +} diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java index d5ecc9cb0..8f66b6233 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java @@ -1,44 +1,47 @@ package eu.dnetlib.dhp.aggregation; +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; +import eu.dnetlib.dhp.collection.GenerateNativeStoreSparkJob; +import eu.dnetlib.dhp.model.mdstore.MetadataRecord; +import eu.dnetlib.dhp.transformation.TransformSparkJobNode; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.io.IntWritable; +import org.apache.hadoop.io.Text; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaSparkContext; +import org.apache.spark.api.java.function.FilterFunction; +import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoder; +import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import org.apache.hadoop.io.IntWritable; -import org.apache.hadoop.io.Text; -import org.apache.spark.SparkConf; -import org.apache.spark.api.java.JavaSparkContext; -import org.apache.spark.api.java.function.MapFunction; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Encoder; -import org.apache.spark.sql.Encoders; -import org.apache.spark.sql.SparkSession; -import org.junit.jupiter.api.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; -import eu.dnetlib.dhp.collection.GenerateNativeStoreSparkJob; -import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; -import eu.dnetlib.dhp.model.mdstore.MetadataRecord; -import eu.dnetlib.dhp.transformation.TransformSparkJobNode; -import eu.dnetlib.dhp.utils.ISLookupClientFactory; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.MDSTORE_DATA_PATH; +import static org.junit.jupiter.api.Assertions.assertEquals; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -public class AggregationJobTest { +@ExtendWith(MockitoExtension.class) +public class AggregationJobTest extends AbstractVocabularyTest{ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @@ -55,6 +58,8 @@ public class AggregationJobTest { private static final Logger log = LoggerFactory.getLogger(AggregationJobTest.class); + + @BeforeAll public static void beforeAll() throws IOException { provenance = IOUtils @@ -81,6 +86,8 @@ public class AggregationJobTest { .getOrCreate(); } + + @AfterAll public static void afterAll() throws IOException { FileUtils.deleteDirectory(workingDir.toFile()); @@ -149,19 +156,45 @@ public class AggregationJobTest { @Order(3) public void testTransformSparkJob() throws Exception { + setUpVocabulary(); + MDStoreVersion mdStoreV2 = prepareVersion("/eu/dnetlib/dhp/collection/mdStoreVersion_2.json"); MDStoreVersion mdStoreCleanedVersion = prepareVersion("/eu/dnetlib/dhp/collection/mdStoreCleanedVersion.json"); - TransformSparkJobNode.main(new String[] { - "-isSparkSessionManaged", Boolean.FALSE.toString(), - "-dateOfTransformation", dateOfCollection, - "-mdstoreInputVersion", OBJECT_MAPPER.writeValueAsString(mdStoreV2), - "-mdstoreOutputVersion", OBJECT_MAPPER.writeValueAsString(mdStoreCleanedVersion), - "-transformationPlugin", "XSLT_TRANSFORM", - "-isLookupUrl", "https://dev-openaire.d4science.org/is/services/isLookUp", - "-transformationRuleId", - "183dde52-a69b-4db9-a07e-1ef2be105294_VHJhbnNmb3JtYXRpb25SdWxlRFNSZXNvdXJjZXMvVHJhbnNmb3JtYXRpb25SdWxlRFNSZXNvdXJjZVR5cGU=" - }); + + mockupTrasformationRule("simpleTRule", "/eu/dnetlib/dhp/transform/ext_simple.xsl"); + + final Map parameters = Stream.of(new String[][] { + { + "dateOfTransformation", "1234" + }, + { + "transformationPlugin", "XSLT_TRANSFORM" + }, + { + "transformationRuleId", "simpleTRule" + }, + + }).collect(Collectors.toMap(data -> data[0], data -> data[1])); + + TransformSparkJobNode.transformRecords(parameters, isLookUpService, spark, mdStoreV2.getHdfsPath()+MDSTORE_DATA_PATH, mdStoreCleanedVersion.getHdfsPath()); + + final Encoder encoder = Encoders.bean(MetadataRecord.class); + final Dataset mOutput = spark.read().format("parquet").load(mdStoreCleanedVersion.getHdfsPath()+MDSTORE_DATA_PATH).as(encoder); + + final Long total = mOutput.count(); + + final long recordTs = mOutput + .filter((FilterFunction) p -> p.getDateOfTransformation() == 1234) + .count(); + + final long recordNotEmpty = mOutput + .filter((FilterFunction) p -> !StringUtils.isBlank(p.getBody())) + .count(); + + assertEquals(total, recordTs); + + assertEquals(total, recordNotEmpty); } diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java index d03c3acef..648d7c8a1 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java @@ -1,20 +1,12 @@ package eu.dnetlib.dhp.transformation; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.Mockito.lenient; - -import java.io.IOException; -import java.io.StringWriter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import javax.xml.transform.stream.StreamSource; - +import eu.dnetlib.dhp.aggregation.AbstractVocabularyTest; +import eu.dnetlib.dhp.aggregation.common.AggregationCounter; +import eu.dnetlib.dhp.collection.CollectionJobTest; +import eu.dnetlib.dhp.model.mdstore.MetadataRecord; +import eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.spark.SparkConf; @@ -24,52 +16,42 @@ import org.apache.spark.sql.Encoder; import org.apache.spark.sql.Encoders; import org.apache.spark.sql.SparkSession; import org.apache.spark.util.LongAccumulator; -import org.dom4j.Document; -import org.dom4j.Node; -import org.dom4j.io.SAXReader; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; -import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import eu.dnetlib.dhp.aggregation.common.AggregationCounter; -import eu.dnetlib.dhp.collection.CollectionJobTest; -import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; -import eu.dnetlib.dhp.model.mdstore.MetadataRecord; -import eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction; -import eu.dnetlib.dhp.utils.ISLookupClientFactory; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.MDSTORE_DATA_PATH; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.lenient; @ExtendWith(MockitoExtension.class) -public class TransformationJobTest { +public class TransformationJobTest extends AbstractVocabularyTest { private static SparkSession spark; - @Mock - private ISLookUpService isLookUpService; - - private VocabularyGroup vocabularies; - - @BeforeEach - public void setUp() throws ISLookUpException, IOException { - lenient().when(isLookUpService.quickSearchProfile(VocabularyGroup.VOCABULARIES_XQUERY)).thenReturn(vocs()); - - lenient() - .when(isLookUpService.quickSearchProfile(VocabularyGroup.VOCABULARY_SYNONYMS_XQUERY)) - .thenReturn(synonyms()); - vocabularies = VocabularyGroup.loadVocsFromIS(isLookUpService); - } - @BeforeAll - public static void beforeAll() { + public static void beforeAll() throws IOException, ISLookUpException { SparkConf conf = new SparkConf(); conf.setAppName(CollectionJobTest.class.getSimpleName()); conf.setMaster("local"); spark = SparkSession.builder().config(conf).getOrCreate(); } + + @BeforeEach + public void setUp() throws IOException, ISLookUpException { + setUpVocabulary(); + } + @AfterAll public static void afterAll() { spark.stop(); @@ -101,8 +83,6 @@ public class TransformationJobTest { mockupTrasformationRule("simpleTRule", "/eu/dnetlib/dhp/transform/ext_simple.xsl"); -// final String arguments = "-issm true -i %s -o %s -d 1 -w 1 -tp XSLT_TRANSFORM -tr simpleTRule"; - final Map parameters = Stream.of(new String[][] { { "dateOfTransformation", "1234" @@ -111,7 +91,7 @@ public class TransformationJobTest { "transformationPlugin", "XSLT_TRANSFORM" }, { - "transformationRuleTitle", "simpleTRule" + "transformationRuleId", "simpleTRule" }, }).collect(Collectors.toMap(data -> data[0], data -> data[1])); @@ -121,7 +101,7 @@ public class TransformationJobTest { // TODO introduce useful assertions final Encoder encoder = Encoders.bean(MetadataRecord.class); - final Dataset mOutput = spark.read().format("parquet").load(mdstore_output).as(encoder); + final Dataset mOutput = spark.read().format("parquet").load(mdstore_output+MDSTORE_DATA_PATH).as(encoder); final Long total = mOutput.count(); @@ -151,13 +131,7 @@ public class TransformationJobTest { Files.deleteIfExists(tempDirWithPrefix); } - private void mockupTrasformationRule(final String trule, final String path) throws Exception { - final String trValue = IOUtils.toString(this.getClass().getResourceAsStream(path)); - lenient() - .when(isLookUpService.quickSearchProfile(String.format(TransformationFactory.TRULE_XQUERY, trule))) - .thenReturn(Collections.singletonList(trValue)); - } private XSLTTransformationFunction loadTransformationRule(final String path) throws Exception { final String trValue = IOUtils.toString(this.getClass().getResourceAsStream(path)); @@ -165,13 +139,5 @@ public class TransformationJobTest { return new XSLTTransformationFunction(new AggregationCounter(la, la, la), trValue, 0, vocabularies); } - private List vocs() throws IOException { - return IOUtils - .readLines(TransformationJobTest.class.getResourceAsStream("/eu/dnetlib/dhp/transform/terms.txt")); - } - private List synonyms() throws IOException { - return IOUtils - .readLines(TransformationJobTest.class.getResourceAsStream("/eu/dnetlib/dhp/transform/synonyms.txt")); - } } From 40764cf626e316f4fba5d999421fdac0ec25c129 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 4 Feb 2021 14:06:02 +0100 Subject: [PATCH 096/445] better logging, WIP: collectorWorker error reporting --- .../dhp/application/ApplicationUtils.java | 6 +- .../ArgumentApplicationParser.java | 20 ++--- .../collection/plugin/oai/OaiIterator.java | 12 ++- .../collection/worker/CollectorWorker.java | 6 +- .../worker/CollectorWorkerApplication.java | 5 +- .../aggregation/AbstractVocabularyTest.java | 68 ++++++++-------- .../dhp/aggregation/AggregationJobTest.java | 80 ++++++++++--------- .../transformation/TransformationJobTest.java | 45 ++++++----- 8 files changed, 125 insertions(+), 117 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/application/ApplicationUtils.java b/dhp-common/src/main/java/eu/dnetlib/dhp/application/ApplicationUtils.java index 531c13af3..72c41a062 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/application/ApplicationUtils.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/application/ApplicationUtils.java @@ -1,14 +1,12 @@ package eu.dnetlib.dhp.application; -import java.io.File; -import java.io.FileOutputStream; -import java.io.OutputStream; +import java.io.*; import java.util.Properties; public class ApplicationUtils { - public static void populateOOZIEEnv(final String paramName, String value) throws Exception { + public static void populateOOZIEEnv(final String paramName, String value) throws IOException { File file = new File(System.getProperty("oozie.action.output.properties")); Properties props = new Properties(); diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/application/ArgumentApplicationParser.java b/dhp-common/src/main/java/eu/dnetlib/dhp/application/ArgumentApplicationParser.java index e65b4bb0b..0429bc25d 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/application/ArgumentApplicationParser.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/application/ArgumentApplicationParser.java @@ -1,10 +1,7 @@ package eu.dnetlib.dhp.application; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.Serializable; -import java.io.StringWriter; +import java.io.*; import java.util.*; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -12,17 +9,21 @@ import java.util.zip.GZIPOutputStream; import org.apache.commons.cli.*; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; public class ArgumentApplicationParser implements Serializable { + private static final Logger log = LoggerFactory.getLogger(ArgumentApplicationParser.class); + private final Options options = new Options(); private final Map objectMap = new HashMap<>(); private final List compressedValues = new ArrayList<>(); - public ArgumentApplicationParser(final String json_configuration) throws Exception { + public ArgumentApplicationParser(final String json_configuration) throws IOException { final ObjectMapper mapper = new ObjectMapper(); final OptionsParameter[] configuration = mapper.readValue(json_configuration, OptionsParameter[].class); createOptionMap(configuration); @@ -33,7 +34,6 @@ public class ArgumentApplicationParser implements Serializable { } private void createOptionMap(final OptionsParameter[] configuration) { - Arrays .stream(configuration) .map( @@ -47,10 +47,6 @@ public class ArgumentApplicationParser implements Serializable { return o; }) .forEach(options::addOption); - - // HelpFormatter formatter = new HelpFormatter(); - // formatter.printHelp("myapp", null, options, null, true); - } public static String decompressValue(final String abstractCompressed) { @@ -61,7 +57,7 @@ public class ArgumentApplicationParser implements Serializable { IOUtils.copy(gis, stringWriter); return stringWriter.toString(); } catch (Throwable e) { - System.out.println("Wrong value to decompress:" + abstractCompressed); + log.error("Wrong value to decompress:" + abstractCompressed); throw new RuntimeException(e); } } @@ -74,7 +70,7 @@ public class ArgumentApplicationParser implements Serializable { return java.util.Base64.getEncoder().encodeToString(out.toByteArray()); } - public void parseArgument(final String[] args) throws Exception { + public void parseArgument(final String[] args) throws ParseException { CommandLineParser parser = new BasicParser(); CommandLine cmd = parser.parse(options, args); Arrays diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java index df0722905..c9cde57ce 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java @@ -113,6 +113,7 @@ public class OaiIterator implements Iterator { return downloadPage(url); } catch (final UnsupportedEncodingException e) { + errorLogList.add(e.getMessage()); throw new CollectorException(e); } } @@ -138,6 +139,7 @@ public class OaiIterator implements Iterator { + "?verb=ListRecords&resumptionToken=" + URLEncoder.encode(resumptionToken, "UTF-8")); } catch (final UnsupportedEncodingException e) { + errorLogList.add(e.getMessage()); throw new CollectorException(e); } } @@ -150,12 +152,14 @@ public class OaiIterator implements Iterator { doc = reader.read(new StringReader(xml)); } catch (final DocumentException e) { log.warn("Error parsing xml, I try to clean it. {}", e.getMessage()); + errorLogList.add(e.getMessage()); final String cleaned = XmlCleaner.cleanAllEntities(xml); try { doc = reader.read(new StringReader(cleaned)); } catch (final DocumentException e1) { final String resumptionToken = extractResumptionToken(xml); if (resumptionToken == null) { + errorLogList.add(e1.getMessage()); throw new CollectorException("Error parsing cleaned document:\n" + cleaned, e1); } return resumptionToken; @@ -166,10 +170,14 @@ public class OaiIterator implements Iterator { if (errorNode != null) { final String code = errorNode.valueOf("@code"); if ("noRecordsMatch".equalsIgnoreCase(code.trim())) { - log.warn("noRecordsMatch for oai call: " + url); + final String msg = "noRecordsMatch for oai call : " + url; + log.warn(msg); + errorLogList.add(msg); return null; } else { - throw new CollectorException(code + " - " + errorNode.getText()); + final String msg = code + " - " + errorNode.getText(); + errorLogList.add(msg); + throw new CollectorException(msg); } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java index 7033cfd8e..f1d3aec9c 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java @@ -29,16 +29,13 @@ public class CollectorWorker { private final String hdfsPath; - private CollectorPlugin plugin; - public CollectorWorker( final ApiDescriptor api, final String hdfsuri, - final String hdfsPath) throws CollectorException { + final String hdfsPath) { this.api = api; this.hdfsuri = hdfsuri; this.hdfsPath = hdfsPath; - this.plugin = CollectorPluginFactory.getPluginByProtocol(api.getProtocol()); } public CollectorPluginErrorLogList collect() throws IOException, CollectorException { @@ -59,6 +56,7 @@ public class CollectorWorker { log.info("Created path " + hdfswritepath.toString()); + final CollectorPlugin plugin = CollectorPluginFactory.getPluginByProtocol(api.getProtocol()); final AtomicInteger counter = new AtomicInteger(0); try (SequenceFile.Writer writer = SequenceFile .createWriter( diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java index d89bcee54..7ec830879 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java @@ -5,6 +5,9 @@ import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.*; import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.*; import static eu.dnetlib.dhp.application.ApplicationUtils.*; +import java.io.IOException; + +import org.apache.commons.cli.ParseException; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,7 +38,7 @@ public class CollectorWorkerApplication { /** * @param args */ - public static void main(final String[] args) throws Exception { + public static void main(final String[] args) throws ParseException, IOException, CollectorException { final ArgumentApplicationParser argumentParser = new ArgumentApplicationParser( IOUtils diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AbstractVocabularyTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AbstractVocabularyTest.java index 84878bd1b..8e0f0ce4b 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AbstractVocabularyTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AbstractVocabularyTest.java @@ -1,52 +1,52 @@ + package eu.dnetlib.dhp.aggregation; +import static org.mockito.Mockito.lenient; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.mockito.Mock; + import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.transformation.TransformationFactory; import eu.dnetlib.dhp.transformation.TransformationJobTest; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import org.apache.commons.io.IOUtils; -import org.mockito.Mock; - -import java.io.IOException; -import java.util.Collections; -import java.util.List; - -import static org.mockito.Mockito.lenient; public abstract class AbstractVocabularyTest { - @Mock - protected ISLookUpService isLookUpService; + @Mock + protected ISLookUpService isLookUpService; - protected VocabularyGroup vocabularies; + protected VocabularyGroup vocabularies; + public void setUpVocabulary() throws ISLookUpException, IOException { + lenient().when(isLookUpService.quickSearchProfile(VocabularyGroup.VOCABULARIES_XQUERY)).thenReturn(vocs()); + lenient() + .when(isLookUpService.quickSearchProfile(VocabularyGroup.VOCABULARY_SYNONYMS_XQUERY)) + .thenReturn(synonyms()); + vocabularies = VocabularyGroup.loadVocsFromIS(isLookUpService); + } - public void setUpVocabulary() throws ISLookUpException, IOException { - lenient().when(isLookUpService.quickSearchProfile(VocabularyGroup.VOCABULARIES_XQUERY)).thenReturn(vocs()); + private static List vocs() throws IOException { + return IOUtils + .readLines(TransformationJobTest.class.getResourceAsStream("/eu/dnetlib/dhp/transform/terms.txt")); + } - lenient() - .when(isLookUpService.quickSearchProfile(VocabularyGroup.VOCABULARY_SYNONYMS_XQUERY)) - .thenReturn(synonyms()); - vocabularies = VocabularyGroup.loadVocsFromIS(isLookUpService); - } + private static List synonyms() throws IOException { + return IOUtils + .readLines(TransformationJobTest.class.getResourceAsStream("/eu/dnetlib/dhp/transform/synonyms.txt")); + } - private static List vocs() throws IOException { - return IOUtils - .readLines(TransformationJobTest.class.getResourceAsStream("/eu/dnetlib/dhp/transform/terms.txt")); - } + protected void mockupTrasformationRule(final String trule, final String path) throws Exception { + final String trValue = IOUtils.toString(this.getClass().getResourceAsStream(path)); - private static List synonyms() throws IOException { - return IOUtils - .readLines(TransformationJobTest.class.getResourceAsStream("/eu/dnetlib/dhp/transform/synonyms.txt")); - } - - protected void mockupTrasformationRule(final String trule, final String path) throws Exception { - final String trValue = IOUtils.toString(this.getClass().getResourceAsStream(path)); - - lenient() - .when(isLookUpService.quickSearchProfile(String.format(TransformationFactory.TRULE_XQUERY, trule))) - .thenReturn(Collections.singletonList(trValue)); - } + lenient() + .when(isLookUpService.quickSearchProfile(String.format(TransformationFactory.TRULE_XQUERY, trule))) + .thenReturn(Collections.singletonList(trValue)); + } } diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java index 8f66b6233..3cb66d5ee 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java @@ -1,12 +1,19 @@ package eu.dnetlib.dhp.aggregation; -import com.fasterxml.jackson.databind.ObjectMapper; -import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; -import eu.dnetlib.dhp.collection.GenerateNativeStoreSparkJob; -import eu.dnetlib.dhp.model.mdstore.MetadataRecord; -import eu.dnetlib.dhp.transformation.TransformSparkJobNode; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.MDSTORE_DATA_PATH; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; @@ -26,22 +33,17 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import com.fasterxml.jackson.databind.ObjectMapper; -import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.MDSTORE_DATA_PATH; -import static org.junit.jupiter.api.Assertions.assertEquals; +import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; +import eu.dnetlib.dhp.collection.GenerateNativeStoreSparkJob; +import eu.dnetlib.dhp.model.mdstore.MetadataRecord; +import eu.dnetlib.dhp.transformation.TransformSparkJobNode; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @ExtendWith(MockitoExtension.class) -public class AggregationJobTest extends AbstractVocabularyTest{ +public class AggregationJobTest extends AbstractVocabularyTest { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @@ -58,8 +60,6 @@ public class AggregationJobTest extends AbstractVocabularyTest{ private static final Logger log = LoggerFactory.getLogger(AggregationJobTest.class); - - @BeforeAll public static void beforeAll() throws IOException { provenance = IOUtils @@ -86,8 +86,6 @@ public class AggregationJobTest extends AbstractVocabularyTest{ .getOrCreate(); } - - @AfterAll public static void afterAll() throws IOException { FileUtils.deleteDirectory(workingDir.toFile()); @@ -161,36 +159,42 @@ public class AggregationJobTest extends AbstractVocabularyTest{ MDStoreVersion mdStoreV2 = prepareVersion("/eu/dnetlib/dhp/collection/mdStoreVersion_2.json"); MDStoreVersion mdStoreCleanedVersion = prepareVersion("/eu/dnetlib/dhp/collection/mdStoreCleanedVersion.json"); - mockupTrasformationRule("simpleTRule", "/eu/dnetlib/dhp/transform/ext_simple.xsl"); final Map parameters = Stream.of(new String[][] { - { - "dateOfTransformation", "1234" - }, - { - "transformationPlugin", "XSLT_TRANSFORM" - }, - { - "transformationRuleId", "simpleTRule" - }, + { + "dateOfTransformation", "1234" + }, + { + "transformationPlugin", "XSLT_TRANSFORM" + }, + { + "transformationRuleId", "simpleTRule" + }, }).collect(Collectors.toMap(data -> data[0], data -> data[1])); - TransformSparkJobNode.transformRecords(parameters, isLookUpService, spark, mdStoreV2.getHdfsPath()+MDSTORE_DATA_PATH, mdStoreCleanedVersion.getHdfsPath()); + TransformSparkJobNode + .transformRecords( + parameters, isLookUpService, spark, mdStoreV2.getHdfsPath() + MDSTORE_DATA_PATH, + mdStoreCleanedVersion.getHdfsPath()); final Encoder encoder = Encoders.bean(MetadataRecord.class); - final Dataset mOutput = spark.read().format("parquet").load(mdStoreCleanedVersion.getHdfsPath()+MDSTORE_DATA_PATH).as(encoder); + final Dataset mOutput = spark + .read() + .format("parquet") + .load(mdStoreCleanedVersion.getHdfsPath() + MDSTORE_DATA_PATH) + .as(encoder); final Long total = mOutput.count(); final long recordTs = mOutput - .filter((FilterFunction) p -> p.getDateOfTransformation() == 1234) - .count(); + .filter((FilterFunction) p -> p.getDateOfTransformation() == 1234) + .count(); final long recordNotEmpty = mOutput - .filter((FilterFunction) p -> !StringUtils.isBlank(p.getBody())) - .count(); + .filter((FilterFunction) p -> !StringUtils.isBlank(p.getBody())) + .count(); assertEquals(total, recordTs); diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java index 648d7c8a1..9d6dacf0c 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java @@ -1,12 +1,18 @@ package eu.dnetlib.dhp.transformation; -import eu.dnetlib.dhp.aggregation.AbstractVocabularyTest; -import eu.dnetlib.dhp.aggregation.common.AggregationCounter; -import eu.dnetlib.dhp.collection.CollectionJobTest; -import eu.dnetlib.dhp.model.mdstore.MetadataRecord; -import eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.MDSTORE_DATA_PATH; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.lenient; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.spark.SparkConf; @@ -21,17 +27,12 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; import org.mockito.junit.jupiter.MockitoExtension; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collections; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.MDSTORE_DATA_PATH; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.lenient; +import eu.dnetlib.dhp.aggregation.AbstractVocabularyTest; +import eu.dnetlib.dhp.aggregation.common.AggregationCounter; +import eu.dnetlib.dhp.collection.CollectionJobTest; +import eu.dnetlib.dhp.model.mdstore.MetadataRecord; +import eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; @ExtendWith(MockitoExtension.class) public class TransformationJobTest extends AbstractVocabularyTest { @@ -46,7 +47,6 @@ public class TransformationJobTest extends AbstractVocabularyTest { spark = SparkSession.builder().config(conf).getOrCreate(); } - @BeforeEach public void setUp() throws IOException, ISLookUpException { setUpVocabulary(); @@ -101,7 +101,11 @@ public class TransformationJobTest extends AbstractVocabularyTest { // TODO introduce useful assertions final Encoder encoder = Encoders.bean(MetadataRecord.class); - final Dataset mOutput = spark.read().format("parquet").load(mdstore_output+MDSTORE_DATA_PATH).as(encoder); + final Dataset mOutput = spark + .read() + .format("parquet") + .load(mdstore_output + MDSTORE_DATA_PATH) + .as(encoder); final Long total = mOutput.count(); @@ -131,13 +135,10 @@ public class TransformationJobTest extends AbstractVocabularyTest { Files.deleteIfExists(tempDirWithPrefix); } - - private XSLTTransformationFunction loadTransformationRule(final String path) throws Exception { final String trValue = IOUtils.toString(this.getClass().getResourceAsStream(path)); final LongAccumulator la = new LongAccumulator(); return new XSLTTransformationFunction(new AggregationCounter(la, la, la), trValue, 0, vocabularies); } - } From 72c57b28fa3ce38524a0caf830cbfa82ae50bd39 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 4 Feb 2021 14:08:18 +0100 Subject: [PATCH 097/445] switched project version to 1.2.4-branch_hadoop_aggregator-SNAPSHOT --- dhp-build/dhp-build-assembly-resources/pom.xml | 2 +- dhp-build/dhp-build-properties-maven-plugin/pom.xml | 2 +- dhp-build/dhp-code-style/pom.xml | 2 +- dhp-build/pom.xml | 2 +- dhp-common/pom.xml | 2 +- dhp-schemas/pom.xml | 2 +- dhp-workflows/dhp-actionmanager/pom.xml | 2 +- dhp-workflows/dhp-aggregation/pom.xml | 2 +- dhp-workflows/dhp-blacklist/pom.xml | 2 +- dhp-workflows/dhp-broker-events/pom.xml | 2 +- dhp-workflows/dhp-dedup-openaire/pom.xml | 2 +- dhp-workflows/dhp-dedup-scholexplorer/pom.xml | 2 +- dhp-workflows/dhp-distcp/pom.xml | 2 +- dhp-workflows/dhp-doiboost/pom.xml | 2 +- dhp-workflows/dhp-enrichment/pom.xml | 2 +- dhp-workflows/dhp-graph-mapper/pom.xml | 2 +- dhp-workflows/dhp-graph-provision-scholexplorer/pom.xml | 2 +- dhp-workflows/dhp-graph-provision/pom.xml | 2 +- dhp-workflows/dhp-stats-promote/pom.xml | 2 +- dhp-workflows/dhp-stats-update/pom.xml | 2 +- dhp-workflows/dhp-usage-raw-data-update/pom.xml | 2 +- dhp-workflows/dhp-usage-stats-build/pom.xml | 2 +- dhp-workflows/dhp-workflow-profiles/pom.xml | 2 +- dhp-workflows/pom.xml | 2 +- pom.xml | 2 +- 25 files changed, 25 insertions(+), 25 deletions(-) diff --git a/dhp-build/dhp-build-assembly-resources/pom.xml b/dhp-build/dhp-build-assembly-resources/pom.xml index 012ff89a3..6298a2e9c 100644 --- a/dhp-build/dhp-build-assembly-resources/pom.xml +++ b/dhp-build/dhp-build-assembly-resources/pom.xml @@ -6,7 +6,7 @@ eu.dnetlib.dhp dhp-build - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT dhp-build-assembly-resources diff --git a/dhp-build/dhp-build-properties-maven-plugin/pom.xml b/dhp-build/dhp-build-properties-maven-plugin/pom.xml index 256017e2c..882d668c9 100644 --- a/dhp-build/dhp-build-properties-maven-plugin/pom.xml +++ b/dhp-build/dhp-build-properties-maven-plugin/pom.xml @@ -6,7 +6,7 @@ eu.dnetlib.dhp dhp-build - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT dhp-build-properties-maven-plugin diff --git a/dhp-build/dhp-code-style/pom.xml b/dhp-build/dhp-code-style/pom.xml index 77aa2aedb..21170ff13 100644 --- a/dhp-build/dhp-code-style/pom.xml +++ b/dhp-build/dhp-code-style/pom.xml @@ -5,7 +5,7 @@ eu.dnetlib.dhp dhp-code-style - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT jar diff --git a/dhp-build/pom.xml b/dhp-build/pom.xml index 12b999b9c..8ee3308cd 100644 --- a/dhp-build/pom.xml +++ b/dhp-build/pom.xml @@ -4,7 +4,7 @@ eu.dnetlib.dhp dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT dhp-build pom diff --git a/dhp-common/pom.xml b/dhp-common/pom.xml index a8607a9b3..e2db8b451 100644 --- a/dhp-common/pom.xml +++ b/dhp-common/pom.xml @@ -5,7 +5,7 @@ eu.dnetlib.dhp dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT ../ diff --git a/dhp-schemas/pom.xml b/dhp-schemas/pom.xml index 73efeabb4..10ee5f9ff 100644 --- a/dhp-schemas/pom.xml +++ b/dhp-schemas/pom.xml @@ -5,7 +5,7 @@ eu.dnetlib.dhp dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT ../pom.xml diff --git a/dhp-workflows/dhp-actionmanager/pom.xml b/dhp-workflows/dhp-actionmanager/pom.xml index 0b4d25700..55eece63d 100644 --- a/dhp-workflows/dhp-actionmanager/pom.xml +++ b/dhp-workflows/dhp-actionmanager/pom.xml @@ -4,7 +4,7 @@ eu.dnetlib.dhp dhp-workflows - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT dhp-actionmanager diff --git a/dhp-workflows/dhp-aggregation/pom.xml b/dhp-workflows/dhp-aggregation/pom.xml index b61c3d443..f0ee42542 100644 --- a/dhp-workflows/dhp-aggregation/pom.xml +++ b/dhp-workflows/dhp-aggregation/pom.xml @@ -4,7 +4,7 @@ eu.dnetlib.dhp dhp-workflows - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT dhp-aggregation diff --git a/dhp-workflows/dhp-blacklist/pom.xml b/dhp-workflows/dhp-blacklist/pom.xml index 9c25f7b29..6312e971a 100644 --- a/dhp-workflows/dhp-blacklist/pom.xml +++ b/dhp-workflows/dhp-blacklist/pom.xml @@ -3,7 +3,7 @@ dhp-workflows eu.dnetlib.dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT 4.0.0 diff --git a/dhp-workflows/dhp-broker-events/pom.xml b/dhp-workflows/dhp-broker-events/pom.xml index 75cc0ea09..29e7c5295 100644 --- a/dhp-workflows/dhp-broker-events/pom.xml +++ b/dhp-workflows/dhp-broker-events/pom.xml @@ -5,7 +5,7 @@ dhp-workflows eu.dnetlib.dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT 4.0.0 diff --git a/dhp-workflows/dhp-dedup-openaire/pom.xml b/dhp-workflows/dhp-dedup-openaire/pom.xml index 03ddbcf4c..39a3c50fc 100644 --- a/dhp-workflows/dhp-dedup-openaire/pom.xml +++ b/dhp-workflows/dhp-dedup-openaire/pom.xml @@ -3,7 +3,7 @@ dhp-workflows eu.dnetlib.dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT 4.0.0 dhp-dedup-openaire diff --git a/dhp-workflows/dhp-dedup-scholexplorer/pom.xml b/dhp-workflows/dhp-dedup-scholexplorer/pom.xml index aa4070b01..6c85a459d 100644 --- a/dhp-workflows/dhp-dedup-scholexplorer/pom.xml +++ b/dhp-workflows/dhp-dedup-scholexplorer/pom.xml @@ -3,7 +3,7 @@ dhp-workflows eu.dnetlib.dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT 4.0.0 diff --git a/dhp-workflows/dhp-distcp/pom.xml b/dhp-workflows/dhp-distcp/pom.xml index 8c10538c0..507cebca5 100644 --- a/dhp-workflows/dhp-distcp/pom.xml +++ b/dhp-workflows/dhp-distcp/pom.xml @@ -3,7 +3,7 @@ dhp-workflows eu.dnetlib.dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT 4.0.0 diff --git a/dhp-workflows/dhp-doiboost/pom.xml b/dhp-workflows/dhp-doiboost/pom.xml index 624dd7b31..b2c6fab53 100644 --- a/dhp-workflows/dhp-doiboost/pom.xml +++ b/dhp-workflows/dhp-doiboost/pom.xml @@ -3,7 +3,7 @@ dhp-workflows eu.dnetlib.dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT 4.0.0 diff --git a/dhp-workflows/dhp-enrichment/pom.xml b/dhp-workflows/dhp-enrichment/pom.xml index d0ab77cc5..47103156c 100644 --- a/dhp-workflows/dhp-enrichment/pom.xml +++ b/dhp-workflows/dhp-enrichment/pom.xml @@ -3,7 +3,7 @@ dhp-workflows eu.dnetlib.dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT 4.0.0 diff --git a/dhp-workflows/dhp-graph-mapper/pom.xml b/dhp-workflows/dhp-graph-mapper/pom.xml index 3e1d84c01..5e8448182 100644 --- a/dhp-workflows/dhp-graph-mapper/pom.xml +++ b/dhp-workflows/dhp-graph-mapper/pom.xml @@ -3,7 +3,7 @@ dhp-workflows eu.dnetlib.dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT 4.0.0 diff --git a/dhp-workflows/dhp-graph-provision-scholexplorer/pom.xml b/dhp-workflows/dhp-graph-provision-scholexplorer/pom.xml index b287e9c88..044850806 100644 --- a/dhp-workflows/dhp-graph-provision-scholexplorer/pom.xml +++ b/dhp-workflows/dhp-graph-provision-scholexplorer/pom.xml @@ -3,7 +3,7 @@ dhp-workflows eu.dnetlib.dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT 4.0.0 diff --git a/dhp-workflows/dhp-graph-provision/pom.xml b/dhp-workflows/dhp-graph-provision/pom.xml index 0d44d8e5e..db473bc0b 100644 --- a/dhp-workflows/dhp-graph-provision/pom.xml +++ b/dhp-workflows/dhp-graph-provision/pom.xml @@ -3,7 +3,7 @@ dhp-workflows eu.dnetlib.dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT 4.0.0 diff --git a/dhp-workflows/dhp-stats-promote/pom.xml b/dhp-workflows/dhp-stats-promote/pom.xml index c64c2f58e..f22c19047 100644 --- a/dhp-workflows/dhp-stats-promote/pom.xml +++ b/dhp-workflows/dhp-stats-promote/pom.xml @@ -3,7 +3,7 @@ dhp-workflows eu.dnetlib.dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT 4.0.0 dhp-stats-promote diff --git a/dhp-workflows/dhp-stats-update/pom.xml b/dhp-workflows/dhp-stats-update/pom.xml index 52f35ff07..0fda05325 100644 --- a/dhp-workflows/dhp-stats-update/pom.xml +++ b/dhp-workflows/dhp-stats-update/pom.xml @@ -3,7 +3,7 @@ dhp-workflows eu.dnetlib.dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT 4.0.0 dhp-stats-update diff --git a/dhp-workflows/dhp-usage-raw-data-update/pom.xml b/dhp-workflows/dhp-usage-raw-data-update/pom.xml index a78f92d41..3d01ad847 100644 --- a/dhp-workflows/dhp-usage-raw-data-update/pom.xml +++ b/dhp-workflows/dhp-usage-raw-data-update/pom.xml @@ -3,7 +3,7 @@ dhp-workflows eu.dnetlib.dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT 4.0.0 dhp-usage-raw-data-update diff --git a/dhp-workflows/dhp-usage-stats-build/pom.xml b/dhp-workflows/dhp-usage-stats-build/pom.xml index 20d2f5b76..bf580ed7f 100644 --- a/dhp-workflows/dhp-usage-stats-build/pom.xml +++ b/dhp-workflows/dhp-usage-stats-build/pom.xml @@ -3,7 +3,7 @@ dhp-workflows eu.dnetlib.dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT 4.0.0 dhp-usage-stats-build diff --git a/dhp-workflows/dhp-workflow-profiles/pom.xml b/dhp-workflows/dhp-workflow-profiles/pom.xml index 54e76c1e2..99a793612 100644 --- a/dhp-workflows/dhp-workflow-profiles/pom.xml +++ b/dhp-workflows/dhp-workflow-profiles/pom.xml @@ -3,7 +3,7 @@ dhp-workflows eu.dnetlib.dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT 4.0.0 diff --git a/dhp-workflows/pom.xml b/dhp-workflows/pom.xml index 190c9847e..4131d79c0 100644 --- a/dhp-workflows/pom.xml +++ b/dhp-workflows/pom.xml @@ -6,7 +6,7 @@ eu.dnetlib.dhp dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index cfe1edfbd..bef649c67 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 eu.dnetlib.dhp dhp - 1.2.4-SNAPSHOT + 1.2.4-branch_hadoop_aggregator-SNAPSHOT pom From 4dae5e605dc48cf7ef5dd57b562d1f7b5938b329 Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Thu, 4 Feb 2021 15:51:15 +0100 Subject: [PATCH 098/445] implemented messaging btween collection worker and Dnet --- .../java/eu/dnetlib/dhp/message/Message.java | 22 ++--- .../eu/dnetlib/dhp/message/MessageSender.java | 87 ++++++++++++------- .../collection/worker/CollectorWorker.java | 13 ++- .../worker/CollectorWorkerApplication.java | 14 ++- .../dhp/collection/collector_parameter.json | 10 ++- .../dhp/collection/oozie_app/workflow.xml | 8 ++ .../DnetCollectorWorkerApplicationTests.java | 11 --- 7 files changed, 105 insertions(+), 60 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java b/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java index 57844d490..978af6dd8 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java @@ -2,10 +2,15 @@ package eu.dnetlib.dhp.message; import java.io.Serializable; +import java.util.HashMap; import java.util.Map; public class Message implements Serializable { + public static String CURRENT_PARAM = "current"; + public static String TOTAL_PARAM = "total"; + + /** * */ @@ -13,17 +18,14 @@ public class Message implements Serializable { private String workflowId; - private String jobName; - private Map body; public Message() { + body = new HashMap<>(); } - public Message(final String workflowId, final String jobName, - final Map body) { + public Message(final String workflowId, final Map body) { this.workflowId = workflowId; - this.jobName = jobName; this.body = body; } @@ -35,14 +37,6 @@ public class Message implements Serializable { this.workflowId = workflowId; } - public String getJobName() { - return jobName; - } - - public void setJobName(final String jobName) { - this.jobName = jobName; - } - public Map getBody() { return body; } @@ -53,6 +47,6 @@ public class Message implements Serializable { @Override public String toString() { - return String.format("Message [workflowId=%s, jobName=%s, body=%s]", workflowId, jobName, body); + return String.format("Message [workflowId=%s, body=%s]", workflowId, body); } } diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageSender.java b/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageSender.java index 70eb594f8..35ecaa50c 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageSender.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageSender.java @@ -10,48 +10,71 @@ import org.apache.http.impl.client.HttpClients; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; + public class MessageSender { - private static final Logger log = LoggerFactory.getLogger(MessageSender.class); + private static final Logger log = LoggerFactory.getLogger(MessageSender.class); - private static final int SOCKET_TIMEOUT_MS = 2000; + private static final int SOCKET_TIMEOUT_MS = 2000; - private static final int CONNECTION_REQUEST_TIMEOUT_MS = 2000; + private static final int CONNECTION_REQUEST_TIMEOUT_MS = 2000; - private static final int CONNTECTION_TIMEOUT_MS = 2000; + private static final int CONNTECTION_TIMEOUT_MS = 2000; - private final String dnetMessageEndpoint; + private final String dnetMessageEndpoint; - public MessageSender(final String dnetMessageEndpoint) { - this.dnetMessageEndpoint = dnetMessageEndpoint; - } + private final String workflowId; - public void sendMessage(final Message message) { - new Thread(() -> _sendMessage(message)).start(); - } - private void _sendMessage(final Message message) { - final HttpPut req = new HttpPut(dnetMessageEndpoint); - req.setEntity(new SerializableEntity(message)); + public MessageSender(final String dnetMessageEndpoint, final String workflowId) { + this.workflowId = workflowId; + this.dnetMessageEndpoint = dnetMessageEndpoint; + } - final RequestConfig requestConfig = RequestConfig - .custom() - .setConnectTimeout(CONNTECTION_TIMEOUT_MS) - .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT_MS) - .setSocketTimeout(SOCKET_TIMEOUT_MS) - .build(); - ; + public void sendMessage(final Message message) { + new Thread(() -> _sendMessage(message)).start(); + } - try (final CloseableHttpClient client = HttpClients - .custom() - .setDefaultRequestConfig(requestConfig) - .build(); - final CloseableHttpResponse response = client.execute(req)) { - log.debug("Sent Message to " + dnetMessageEndpoint); - log.debug("MESSAGE:" + message); - } catch (final Throwable e) { - log.error("Error sending message to " + dnetMessageEndpoint + ", message content: " + message, e); - } - } + public void sendMessage(final Long current, final Long total) { + sendMessage(createMessage(current, total)); + } + + + private Message createMessage(final Long current, final Long total) { + + final Message m = new Message(); + m.setWorkflowId(workflowId); + m.getBody().put(Message.CURRENT_PARAM, current.toString()); + if (total != null) + m.getBody().put(Message.TOTAL_PARAM, total.toString()); + return m; + } + + + private void _sendMessage(final Message message) { + final HttpPut req = new HttpPut(dnetMessageEndpoint); + req.setEntity(new SerializableEntity(message)); + + final RequestConfig requestConfig = RequestConfig + .custom() + .setConnectTimeout(CONNTECTION_TIMEOUT_MS) + .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT_MS) + .setSocketTimeout(SOCKET_TIMEOUT_MS) + .build(); + ; + + try (final CloseableHttpClient client = HttpClients + .custom() + .setDefaultRequestConfig(requestConfig) + .build(); + final CloseableHttpResponse response = client.execute(req)) { + log.debug("Sent Message to " + dnetMessageEndpoint); + log.debug("MESSAGE:" + message); + } catch (final Throwable e) { + log.error("Error sending message to " + dnetMessageEndpoint + ", message content: " + message, e); + } + } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java index f1d3aec9c..9d82a1ed4 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java @@ -5,6 +5,8 @@ import java.io.IOException; import java.net.URI; import java.util.concurrent.atomic.AtomicInteger; +import eu.dnetlib.dhp.message.Message; +import eu.dnetlib.dhp.message.MessageSender; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; @@ -29,13 +31,18 @@ public class CollectorWorker { private final String hdfsPath; + private final MessageSender messageSender; + + public CollectorWorker( final ApiDescriptor api, final String hdfsuri, - final String hdfsPath) { + final String hdfsPath, + final MessageSender messageSender) { this.api = api; this.hdfsuri = hdfsuri; this.hdfsPath = hdfsPath; + this.messageSender = messageSender; } public CollectorPluginErrorLogList collect() throws IOException, CollectorException { @@ -58,6 +65,7 @@ public class CollectorWorker { final CollectorPlugin plugin = CollectorPluginFactory.getPluginByProtocol(api.getProtocol()); final AtomicInteger counter = new AtomicInteger(0); + try (SequenceFile.Writer writer = SequenceFile .createWriter( conf, @@ -71,6 +79,8 @@ public class CollectorWorker { .forEach( content -> { key.set(counter.getAndIncrement()); + if (counter.get()% 500 == 0) + messageSender.sendMessage(counter.longValue(), null); value.set(content); try { writer.append(key, value); @@ -79,6 +89,7 @@ public class CollectorWorker { } }); } finally { + messageSender.sendMessage(counter.longValue(),counter.longValue()); return plugin.getCollectionErrors(); } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java index 7ec830879..d8e8a8e49 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java @@ -7,6 +7,8 @@ import static eu.dnetlib.dhp.application.ApplicationUtils.*; import java.io.IOException; +import eu.dnetlib.dhp.message.Message; +import eu.dnetlib.dhp.message.MessageSender; import org.apache.commons.cli.ParseException; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; @@ -57,17 +59,27 @@ public class CollectorWorkerApplication { final String mdStoreVersion = argumentParser.get("mdStoreVersion"); log.info("mdStoreVersion is {}", mdStoreVersion); + final String dnetMessageManagerURL = argumentParser.get("dnetMessageManagerURL"); + log.info("dnetMessageManagerURL is {}", dnetMessageManagerURL); + + final String workflowId = argumentParser.get("workflowId"); + log.info("workflowId is {}", workflowId); + + final MessageSender ms = new MessageSender(dnetMessageManagerURL,workflowId); + final MDStoreVersion currentVersion = MAPPER.readValue(mdStoreVersion, MDStoreVersion.class); final String hdfsPath = currentVersion.getHdfsPath() + SEQUENCE_FILE_NAME; log.info("hdfs path is {}", hdfsPath); final ApiDescriptor api = MAPPER.readValue(apiDescriptor, ApiDescriptor.class); - final CollectorWorker worker = new CollectorWorker(api, hdfsuri, hdfsPath); + final CollectorWorker worker = new CollectorWorker(api, hdfsuri, hdfsPath, ms); CollectorPluginErrorLogList errors = worker.collect(); populateOOZIEEnv(COLLECTOR_WORKER_ERRORS, errors.toString()); } + + } diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_parameter.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_parameter.json index 60e9762ff..6ccba468a 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_parameter.json +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_parameter.json @@ -17,10 +17,18 @@ "paramDescription": "the MDStore Version bean", "paramRequired": true }, + { + "paramName": "dm", + "paramLongName": "dnetMessageManagerURL", + "paramDescription": "the End point URL to send Messages", + "paramRequired": true + }, + + { "paramName": "w", "paramLongName": "workflowId", "paramDescription": "the identifier of the dnet Workflow", - "paramRequired": false + "paramRequired": true } ] \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml index 595613a2e..b74ef6b61 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml @@ -32,6 +32,11 @@ mdStoreManagerURI The URI of the MDStore Manager + + + dnetMessageManagerURL + The URI of the Dnet Message Manager + collectionMode Should be REFRESH or INCREMENTAL @@ -86,7 +91,10 @@ eu.dnetlib.dhp.collection.worker.CollectorWorkerApplication --apidescriptor${apiDescription} --namenode${nameNode} + --workflowId${workflowId} + --dnetMessageManagerURL${dnetMessageManagerURL} --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} + diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/DnetCollectorWorkerApplicationTests.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/DnetCollectorWorkerApplicationTests.java index 10964096c..9066e32b6 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/DnetCollectorWorkerApplicationTests.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/DnetCollectorWorkerApplicationTests.java @@ -36,17 +36,6 @@ public class DnetCollectorWorkerApplicationTests { assertNotNull(mapper.writeValueAsString(api)); } - @Test - public void testFeeding(@TempDir Path testDir) throws Exception { - - System.out.println(testDir.toString()); - CollectorWorker worker = new CollectorWorker(getApi(), - "file://" + testDir.toString() + "/file.seq", testDir.toString() + "/file.seq"); - worker.collect(); - - // TODO create ASSERT HERE - } - private ApiDescriptor getApi() { final ApiDescriptor api = new ApiDescriptor(); api.setId("oai"); From deb85706db53e4cd304ae323805e518d06570be2 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 4 Feb 2021 17:24:52 +0100 Subject: [PATCH 099/445] imported HttpConnector from https://svn.driver.research-infrastructures.eu/driver/dnet45/modules/dnet-modular-collector-service/trunk/src/main/java/eu/dnetlib/data/collector/plugins/HttpConnector.java as HttpConnector2 --- .../dhp/model/mdstore/MetadataRecordTest.java | 16 - .../actionmanager/project/utils/ReadCSV.java | 4 +- .../project/utils/ReadExcel.java | 5 +- .../collection/plugin/oai/OaiIterator.java | 8 +- .../plugin/oai/OaiIteratorFactory.java | 8 +- .../worker/utils/HttpConnector.java | 8 +- .../worker/utils/HttpConnector2.java | 298 ++++++++++++++++++ .../project/EXCELParserTest.java | 4 +- .../httpconnector/HttpConnectorTest.java | 6 +- ...a => CollectorWorkerApplicationTests.java} | 2 +- 10 files changed, 316 insertions(+), 43 deletions(-) delete mode 100644 dhp-common/src/test/java/eu/dnetlib/dhp/model/mdstore/MetadataRecordTest.java create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector2.java rename dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/{DnetCollectorWorkerApplicationTests.java => CollectorWorkerApplicationTests.java} (97%) diff --git a/dhp-common/src/test/java/eu/dnetlib/dhp/model/mdstore/MetadataRecordTest.java b/dhp-common/src/test/java/eu/dnetlib/dhp/model/mdstore/MetadataRecordTest.java deleted file mode 100644 index cb4d0ab50..000000000 --- a/dhp-common/src/test/java/eu/dnetlib/dhp/model/mdstore/MetadataRecordTest.java +++ /dev/null @@ -1,16 +0,0 @@ - -package eu.dnetlib.dhp.model.mdstore; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -public class MetadataRecordTest { - - @Test - public void getTimestamp() { - - MetadataRecord r = new MetadataRecord(); - assertTrue(r.getDateOfCollection() > 0); - } -} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java index ca1c10611..1b9e070fe 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java @@ -18,7 +18,7 @@ import org.apache.hadoop.fs.Path; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; +import eu.dnetlib.dhp.collection.worker.utils.HttpConnector2; /** * Applies the parsing of a csv file and writes the Serialization of it in hdfs @@ -74,7 +74,7 @@ public class ReadCSV implements Closeable { throws Exception { this.conf = new Configuration(); this.conf.set("fs.defaultFS", hdfsNameNode); - HttpConnector httpConnector = new HttpConnector(); + HttpConnector2 httpConnector = new HttpConnector2(); FileSystem fileSystem = FileSystem.get(this.conf); Path hdfsWritePath = new Path(hdfsPath); FSDataOutputStream fsDataOutputStream = null; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java index 585a408f3..2ad3f5b34 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java @@ -15,12 +15,11 @@ import org.apache.hadoop.fs.Path; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; +import eu.dnetlib.dhp.collection.worker.utils.HttpConnector2; /** * Applies the parsing of an excel file and writes the Serialization of it in hdfs */ - public class ReadExcel implements Closeable { private static final Log log = LogFactory.getLog(ReadCSV.class); private final Configuration conf; @@ -72,7 +71,7 @@ public class ReadExcel implements Closeable { throws Exception { this.conf = new Configuration(); this.conf.set("fs.defaultFS", hdfsNameNode); - HttpConnector httpConnector = new HttpConnector(); + HttpConnector2 httpConnector = new HttpConnector2(); FileSystem fileSystem = FileSystem.get(this.conf); Path hdfsWritePath = new Path(hdfsPath); FSDataOutputStream fsDataOutputStream = null; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java index c9cde57ce..9c1ff0663 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java @@ -9,8 +9,6 @@ import java.util.Queue; import java.util.concurrent.PriorityBlockingQueue; import org.apache.commons.lang.StringUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Node; @@ -20,7 +18,7 @@ import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.collection.worker.CollectorException; import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginErrorLogList; -import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; +import eu.dnetlib.dhp.collection.worker.utils.HttpConnector2; import eu.dnetlib.dhp.collection.worker.utils.XmlCleaner; public class OaiIterator implements Iterator { @@ -37,7 +35,7 @@ public class OaiIterator implements Iterator { private final String untilDate; private String token; private boolean started; - private final HttpConnector httpConnector; + private final HttpConnector2 httpConnector; private CollectorPluginErrorLogList errorLogList; public OaiIterator( @@ -46,7 +44,7 @@ public class OaiIterator implements Iterator { final String set, final String fromDate, final String untilDate, - final HttpConnector httpConnector, + final HttpConnector2 httpConnector, final CollectorPluginErrorLogList errorLogList) { this.baseUrl = baseUrl; this.mdFormat = mdFormat; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java index eafd265d4..d6759580f 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java @@ -4,11 +4,11 @@ package eu.dnetlib.dhp.collection.plugin.oai; import java.util.Iterator; import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginErrorLogList; -import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; +import eu.dnetlib.dhp.collection.worker.utils.HttpConnector2; public class OaiIteratorFactory { - private HttpConnector httpConnector; + private HttpConnector2 httpConnector; public Iterator newIterator( final String baseUrl, @@ -20,9 +20,9 @@ public class OaiIteratorFactory { return new OaiIterator(baseUrl, mdFormat, set, fromDate, untilDate, getHttpConnector(), errorLogList); } - private HttpConnector getHttpConnector() { + private HttpConnector2 getHttpConnector() { if (httpConnector == null) - httpConnector = new HttpConnector(); + httpConnector = new HttpConnector2(); return httpConnector; } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector.java index fc45b4814..39c2371b9 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector.java @@ -4,16 +4,9 @@ package eu.dnetlib.dhp.collection.worker.utils; import java.io.IOException; import java.io.InputStream; import java.net.*; -import java.security.GeneralSecurityException; -import java.security.cert.X509Certificate; import java.util.List; import java.util.Map; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - import org.apache.commons.io.IOUtils; import org.apache.commons.lang.math.NumberUtils; import org.slf4j.Logger; @@ -21,6 +14,7 @@ import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.collection.worker.CollectorException; +@Deprecated public class HttpConnector { private static final Logger log = LoggerFactory.getLogger(HttpConnector.class); diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector2.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector2.java new file mode 100644 index 000000000..b316f34ed --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector2.java @@ -0,0 +1,298 @@ + +package eu.dnetlib.dhp.collection.worker.utils; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.*; +import java.security.GeneralSecurityException; +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Map; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import eu.dnetlib.dhp.collection.worker.CollectorException; + +/** + * Migrated from https://svn.driver.research-infrastructures.eu/driver/dnet45/modules/dnet-modular-collector-service/trunk/src/main/java/eu/dnetlib/data/collector/plugins/HttpConnector.java + * + * @author jochen, michele, andrea, alessia + */ +public class HttpConnector2 { + + private static final Log log = LogFactory.getLog(HttpConnector.class); + + private int maxNumberOfRetry = 6; + private int defaultDelay = 120; // seconds + private int readTimeOut = 120; // seconds + + private String responseType = null; + + private String userAgent = "Mozilla/5.0 (compatible; OAI; +http://www.openaire.eu)"; + + public HttpConnector2() { + CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL)); + } + + /** + * @see HttpConnector2#getInputSource(java.lang.String, eu.dnetlib.dhp.collection.worker.utils.CollectorPluginErrorLogList) + */ + public String getInputSource(final String requestUrl) throws CollectorException { + return attemptDownlaodAsString(requestUrl, 1, new CollectorPluginErrorLogList()); + } + + /** + * @see HttpConnector2#getInputSource(java.lang.String, eu.dnetlib.dhp.collection.worker.utils.CollectorPluginErrorLogList) + */ + public InputStream getInputSourceAsStream(final String requestUrl) throws CollectorException { + return IOUtils.toInputStream(getInputSource(requestUrl)); + } + + /** + * Given the URL returns the content via HTTP GET + * + * @param requestUrl the URL + * @param errorLogList the list of errors + * @return the content of the downloaded resource + * @throws CollectorException when retrying more than maxNumberOfRetry times + */ + public String getInputSource(final String requestUrl, CollectorPluginErrorLogList errorLogList) + throws CollectorException { + return attemptDownlaodAsString(requestUrl, 1, errorLogList); + } + + private String attemptDownlaodAsString(final String requestUrl, final int retryNumber, + final CollectorPluginErrorLogList errorList) + throws CollectorException { + try { + InputStream s = attemptDownload(requestUrl, 1, new CollectorPluginErrorLogList()); + try { + return IOUtils.toString(s); + } catch (IOException e) { + log.error("error while retrieving from http-connection occured: " + requestUrl, e); + Thread.sleep(defaultDelay * 1000); + errorList.add(e.getMessage()); + return attemptDownlaodAsString(requestUrl, retryNumber + 1, errorList); + } finally { + IOUtils.closeQuietly(s); + } + } catch (InterruptedException e) { + throw new CollectorException(e); + } + } + + private InputStream attemptDownload(final String requestUrl, final int retryNumber, + final CollectorPluginErrorLogList errorList) + throws CollectorException { + + if (retryNumber > maxNumberOfRetry) { + throw new CollectorException("Max number of retries exceeded. Cause: \n " + errorList); + } + + log.debug("Downloading " + requestUrl + " - try: " + retryNumber); + try { + InputStream input = null; + + try { + final HttpURLConnection urlConn = (HttpURLConnection) new URL(requestUrl).openConnection(); + urlConn.setInstanceFollowRedirects(false); + urlConn.setReadTimeout(readTimeOut * 1000); + urlConn.addRequestProperty("User-Agent", userAgent); + + if (log.isDebugEnabled()) { + logHeaderFields(urlConn); + } + + int retryAfter = obtainRetryAfter(urlConn.getHeaderFields()); + if (is2xx(urlConn.getResponseCode())) { + input = urlConn.getInputStream(); + responseType = urlConn.getContentType(); + return input; + } + if (is3xx(urlConn.getResponseCode())) { + // REDIRECTS + final String newUrl = obtainNewLocation(urlConn.getHeaderFields()); + log.debug(String.format("The requested url %s has been moved to %s", requestUrl, newUrl)); + errorList + .add( + String + .format( + "%s %s %s. Moved to: %s", requestUrl, urlConn.getResponseCode(), + urlConn.getResponseMessage(), newUrl)); + urlConn.disconnect(); + if (retryAfter > 0) + Thread.sleep(retryAfter * 1000); + return attemptDownload(newUrl, retryNumber + 1, errorList); + } + if (is4xx(urlConn.getResponseCode())) { + // CLIENT ERROR, DO NOT RETRY + errorList + .add( + String + .format( + "%s error %s: %s", requestUrl, urlConn.getResponseCode(), + urlConn.getResponseMessage())); + throw new CollectorException("4xx error: request will not be repeated. " + errorList); + } + if (is5xx(urlConn.getResponseCode())) { + // SERVER SIDE ERRORS RETRY ONLY on 503 + switch (urlConn.getResponseCode()) { + case HttpURLConnection.HTTP_UNAVAILABLE: + if (retryAfter > 0) { + log + .warn( + requestUrl + " - waiting and repeating request after suggested retry-after " + + retryAfter + " sec."); + Thread.sleep(retryAfter * 1000); + } else { + log + .warn( + requestUrl + " - waiting and repeating request after default delay of " + + defaultDelay + " sec."); + Thread.sleep(defaultDelay * 1000); + } + errorList.add(requestUrl + " 503 Service Unavailable"); + urlConn.disconnect(); + return attemptDownload(requestUrl, retryNumber + 1, errorList); + default: + errorList + .add( + String + .format( + "%s Error %s: %s", requestUrl, urlConn.getResponseCode(), + urlConn.getResponseMessage())); + throw new CollectorException(urlConn.getResponseCode() + " error " + errorList); + } + } + throw new CollectorException( + String.format("Unexpected status code: %s error %s", urlConn.getResponseCode(), errorList)); + } catch (MalformedURLException | NoRouteToHostException e) { + errorList.add(String.format("Error: %s for request url: %s", e.getCause(), requestUrl)); + throw new CollectorException(e + "error " + errorList); + } catch (IOException e) { + Thread.sleep(defaultDelay * 1000); + errorList.add(requestUrl + " " + e.getMessage()); + return attemptDownload(requestUrl, retryNumber + 1, errorList); + } + } catch (InterruptedException e) { + throw new CollectorException(e); + } + } + + private void logHeaderFields(final HttpURLConnection urlConn) throws IOException { + log.debug("StatusCode: " + urlConn.getResponseMessage()); + + for (Map.Entry> e : urlConn.getHeaderFields().entrySet()) { + if (e.getKey() != null) { + for (String v : e.getValue()) { + log.debug(" key: " + e.getKey() + " - value: " + v); + } + } + } + } + + private int obtainRetryAfter(final Map> headerMap) { + for (String key : headerMap.keySet()) { + if ((key != null) && key.toLowerCase().equals("retry-after") && (headerMap.get(key).size() > 0) + && NumberUtils.isCreatable(headerMap.get(key).get(0))) { + return Integer + .parseInt(headerMap.get(key).get(0)) + 10; + } + } + return -1; + } + + private String obtainNewLocation(final Map> headerMap) throws CollectorException { + for (String key : headerMap.keySet()) { + if ((key != null) && key.toLowerCase().equals("location") && (headerMap.get(key).size() > 0)) { + return headerMap.get(key).get(0); + } + } + throw new CollectorException("The requested url has been MOVED, but 'location' param is MISSING"); + } + + /** + * register for https scheme; this is a workaround and not intended for the use in trusted environments + */ + public void initTrustManager() { + final X509TrustManager tm = new X509TrustManager() { + + @Override + public void checkClientTrusted(final X509Certificate[] xcs, final String string) { + } + + @Override + public void checkServerTrusted(final X509Certificate[] xcs, final String string) { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + }; + try { + final SSLContext ctx = SSLContext.getInstance("TLS"); + ctx.init(null, new TrustManager[] { + tm + }, null); + HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory()); + } catch (GeneralSecurityException e) { + log.fatal(e); + throw new IllegalStateException(e); + } + } + + private boolean is2xx(final int statusCode) { + return statusCode >= 200 && statusCode <= 299; + } + + private boolean is4xx(final int statusCode) { + return statusCode >= 400 && statusCode <= 499; + } + + private boolean is3xx(final int statusCode) { + return statusCode >= 300 && statusCode <= 399; + } + + private boolean is5xx(final int statusCode) { + return statusCode >= 500 && statusCode <= 599; + } + + public int getMaxNumberOfRetry() { + return maxNumberOfRetry; + } + + public void setMaxNumberOfRetry(final int maxNumberOfRetry) { + this.maxNumberOfRetry = maxNumberOfRetry; + } + + public int getDefaultDelay() { + return defaultDelay; + } + + public void setDefaultDelay(final int defaultDelay) { + this.defaultDelay = defaultDelay; + } + + public int getReadTimeOut() { + return readTimeOut; + } + + public void setReadTimeOut(final int readTimeOut) { + this.readTimeOut = readTimeOut; + } + + public String getResponseType() { + return responseType; + } + +} diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java index 5c37e9ec3..e6fda9be0 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java @@ -14,13 +14,13 @@ import org.junit.jupiter.api.Test; import eu.dnetlib.dhp.actionmanager.project.utils.EXCELParser; import eu.dnetlib.dhp.collection.worker.CollectorException; -import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; +import eu.dnetlib.dhp.collection.worker.utils.HttpConnector2; @Disabled public class EXCELParserTest { private static Path workingDir; - private HttpConnector httpConnector = new HttpConnector(); + private HttpConnector2 httpConnector = new HttpConnector2(); private static final String URL = "http://cordis.europa.eu/data/reference/cordisref-H2020topics.xlsx"; @BeforeAll diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java index f5ef280a0..103e11c33 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java @@ -10,13 +10,13 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import eu.dnetlib.dhp.collection.worker.CollectorException; -import eu.dnetlib.dhp.collection.worker.utils.HttpConnector; +import eu.dnetlib.dhp.collection.worker.utils.HttpConnector2; @Disabled public class HttpConnectorTest { private static final Log log = LogFactory.getLog(HttpConnectorTest.class); - private static HttpConnector connector; + private static HttpConnector2 connector; private static final String URL = "http://cordis.europa.eu/data/reference/cordisref-H2020topics.xlsx"; private static final String URL_MISCONFIGURED_SERVER = "https://www.alexandria.unisg.ch/cgi/oai2?verb=Identify"; @@ -27,7 +27,7 @@ public class HttpConnectorTest { @BeforeAll public static void setUp() { - connector = new HttpConnector(); + connector = new HttpConnector2(); } @Test diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/DnetCollectorWorkerApplicationTests.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/CollectorWorkerApplicationTests.java similarity index 97% rename from dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/DnetCollectorWorkerApplicationTests.java rename to dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/CollectorWorkerApplicationTests.java index 10964096c..ff9ad8c85 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/DnetCollectorWorkerApplicationTests.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/CollectorWorkerApplicationTests.java @@ -16,7 +16,7 @@ import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginFactory; import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; @Disabled -public class DnetCollectorWorkerApplicationTests { +public class CollectorWorkerApplicationTests { @Test public void testFindPlugin() throws Exception { From 2ee0c3e47e4b17a7902de5b4f90e215c83bdd763 Mon Sep 17 00:00:00 2001 From: "michele.artini" Date: Fri, 5 Feb 2021 09:45:39 +0100 Subject: [PATCH 100/445] http entity as json string --- .../java/eu/dnetlib/dhp/message/Message.java | 1 - .../eu/dnetlib/dhp/message/MessageSender.java | 105 ++++++++++-------- 2 files changed, 56 insertions(+), 50 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java b/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java index 978af6dd8..ed2a3c9b3 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java @@ -10,7 +10,6 @@ public class Message implements Serializable { public static String CURRENT_PARAM = "current"; public static String TOTAL_PARAM = "total"; - /** * */ diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageSender.java b/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageSender.java index 35ecaa50c..3f9d07a7e 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageSender.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageSender.java @@ -4,77 +4,84 @@ package eu.dnetlib.dhp.message; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPut; -import org.apache.http.entity.SerializableEntity; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; public class MessageSender { - private static final Logger log = LoggerFactory.getLogger(MessageSender.class); + private static final Logger log = LoggerFactory.getLogger(MessageSender.class); - private static final int SOCKET_TIMEOUT_MS = 2000; + private static final int SOCKET_TIMEOUT_MS = 2000; - private static final int CONNECTION_REQUEST_TIMEOUT_MS = 2000; + private static final int CONNECTION_REQUEST_TIMEOUT_MS = 2000; - private static final int CONNTECTION_TIMEOUT_MS = 2000; + private static final int CONNTECTION_TIMEOUT_MS = 2000; - private final String dnetMessageEndpoint; + private final ObjectMapper objectMapper = new ObjectMapper(); - private final String workflowId; + private final String dnetMessageEndpoint; + private final String workflowId; - public MessageSender(final String dnetMessageEndpoint, final String workflowId) { - this.workflowId = workflowId; - this.dnetMessageEndpoint = dnetMessageEndpoint; - } + public MessageSender(final String dnetMessageEndpoint, final String workflowId) { + this.workflowId = workflowId; + this.dnetMessageEndpoint = dnetMessageEndpoint; + } - public void sendMessage(final Message message) { - new Thread(() -> _sendMessage(message)).start(); - } + public void sendMessage(final Message message) { + new Thread(() -> _sendMessage(message)).start(); + } - public void sendMessage(final Long current, final Long total) { - sendMessage(createMessage(current, total)); - } + public void sendMessage(final Long current, final Long total) { + sendMessage(createMessage(current, total)); + } + private Message createMessage(final Long current, final Long total) { - private Message createMessage(final Long current, final Long total) { + final Message m = new Message(); + m.setWorkflowId(workflowId); + m.getBody().put(Message.CURRENT_PARAM, current.toString()); + if (total != null) { + m.getBody().put(Message.TOTAL_PARAM, total.toString()); + } + return m; + } - final Message m = new Message(); - m.setWorkflowId(workflowId); - m.getBody().put(Message.CURRENT_PARAM, current.toString()); - if (total != null) - m.getBody().put(Message.TOTAL_PARAM, total.toString()); - return m; - } + private void _sendMessage(final Message message) { + try { + final String json = objectMapper.writeValueAsString(message); + final HttpPut req = new HttpPut(dnetMessageEndpoint); + req.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON)); - private void _sendMessage(final Message message) { - final HttpPut req = new HttpPut(dnetMessageEndpoint); - req.setEntity(new SerializableEntity(message)); + final RequestConfig requestConfig = RequestConfig + .custom() + .setConnectTimeout(CONNTECTION_TIMEOUT_MS) + .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT_MS) + .setSocketTimeout(SOCKET_TIMEOUT_MS) + .build(); + ; - final RequestConfig requestConfig = RequestConfig - .custom() - .setConnectTimeout(CONNTECTION_TIMEOUT_MS) - .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT_MS) - .setSocketTimeout(SOCKET_TIMEOUT_MS) - .build(); - ; - - try (final CloseableHttpClient client = HttpClients - .custom() - .setDefaultRequestConfig(requestConfig) - .build(); - final CloseableHttpResponse response = client.execute(req)) { - log.debug("Sent Message to " + dnetMessageEndpoint); - log.debug("MESSAGE:" + message); - } catch (final Throwable e) { - log.error("Error sending message to " + dnetMessageEndpoint + ", message content: " + message, e); - } - } + try (final CloseableHttpClient client = HttpClients + .custom() + .setDefaultRequestConfig(requestConfig) + .build(); + final CloseableHttpResponse response = client.execute(req)) { + log.debug("Sent Message to " + dnetMessageEndpoint); + log.debug("MESSAGE:" + message); + } catch (final Throwable e) { + log.error("Error sending message to " + dnetMessageEndpoint + ", message content: " + message, e); + } + } catch (final JsonProcessingException e) { + log.error("Error sending message to " + dnetMessageEndpoint + ", message content: " + message, e); + } + } } From a8a758925e236d70d9fb4ccb2404ef95cd0f9370 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Fri, 5 Feb 2021 19:18:05 +0100 Subject: [PATCH 101/445] better logging, WIP: collectorWorker error reporting --- .../dhp/application/ApplicationUtils.java | 35 +- .../common/AggregationConstants.java | 1 + .../common/AggregationUtility.java | 28 +- .../GenerateNativeStoreSparkJob.java | 8 +- .../collection/plugin/CollectorPlugin.java | 5 +- .../plugin/oai/OaiCollectorPlugin.java | 32 +- .../collection/plugin/oai/OaiIterator.java | 26 +- .../plugin/oai/OaiIteratorFactory.java | 13 +- .../collection/worker/CollectorWorker.java | 76 +++-- .../worker/CollectorWorkerApplication.java | 80 ++++- .../worker/CollectorWorkerReporter.java | 69 ++++ .../utils/CollectorPluginErrorLogList.java | 19 -- .../worker/utils/CollectorPluginFactory.java | 9 +- .../worker/utils/CollectorPluginReport.java | 64 ++++ .../worker/utils/HttpClientParams.java | 62 ++++ .../worker/utils/HttpConnector.java | 218 ------------ .../worker/utils/HttpConnector2.java | 319 +++++++----------- .../UnknownCollectorPluginException.java | 32 ++ .../transformation/TransformSparkJobNode.java | 3 +- .../collector_reporter_input_parameter.json | 14 + ... => collector_worker_input_parameter.json} | 26 +- ... => generate_native_input_parameters.json} | 0 .../dhp/collection/oozie_app/workflow.xml | 15 +- .../httpconnector/HttpConnectorTest.java | 44 --- .../CollectorWorkerApplicationTests.java | 6 +- .../utils/CollectorPluginReportTest.java | 29 ++ 26 files changed, 650 insertions(+), 583 deletions(-) create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerReporter.java delete mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginErrorLogList.java create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginReport.java create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpClientParams.java delete mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector.java create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/UnknownCollectorPluginException.java create mode 100644 dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_reporter_input_parameter.json rename dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/{collector_parameter.json => collector_worker_input_parameter.json} (52%) rename dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/{collection_input_parameters.json => generate_native_input_parameters.json} (100%) delete mode 100644 dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java create mode 100644 dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/utils/CollectorPluginReportTest.java diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/application/ApplicationUtils.java b/dhp-common/src/main/java/eu/dnetlib/dhp/application/ApplicationUtils.java index 72c41a062..c78fb1b1f 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/application/ApplicationUtils.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/application/ApplicationUtils.java @@ -2,18 +2,43 @@ package eu.dnetlib.dhp.application; import java.io.*; +import java.util.Map; import java.util.Properties; +import org.apache.hadoop.conf.Configuration; + +import com.google.common.collect.Maps; + public class ApplicationUtils { - public static void populateOOZIEEnv(final String paramName, String value) throws IOException { + public static Configuration getHadoopConfiguration(String nameNode) { + // ====== Init HDFS File System Object + Configuration conf = new Configuration(); + // Set FileSystem URI + conf.set("fs.defaultFS", nameNode); + // Because of Maven + conf.set("fs.hdfs.impl", org.apache.hadoop.hdfs.DistributedFileSystem.class.getName()); + conf.set("fs.file.impl", org.apache.hadoop.fs.LocalFileSystem.class.getName()); + + System.setProperty("hadoop.home.dir", "/"); + return conf; + } + + public static void populateOOZIEEnv(final Map report) throws IOException { File file = new File(System.getProperty("oozie.action.output.properties")); Properties props = new Properties(); + report.forEach((k, v) -> props.setProperty(k, v)); - props.setProperty(paramName, value); - OutputStream os = new FileOutputStream(file); - props.store(os, ""); - os.close(); + try(OutputStream os = new FileOutputStream(file)) { + props.store(os, ""); + } + } + + public static void populateOOZIEEnv(final String paramName, String value) throws IOException { + Map report = Maps.newHashMap(); + report.put(paramName, value); + + populateOOZIEEnv(report); } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationConstants.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationConstants.java index 7c5ad354d..8e0b7260d 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationConstants.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationConstants.java @@ -4,6 +4,7 @@ package eu.dnetlib.dhp.aggregation.common; public class AggregationConstants { public static final String SEQUENCE_FILE_NAME = "/sequence_file"; + public static final String REPORT_FILE_NAME = "/report"; public static final String MDSTORE_DATA_PATH = "/store"; public static final String MDSTORE_SIZE_PATH = "/size"; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java index 7332ac071..8dad5bb81 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java @@ -2,9 +2,14 @@ package eu.dnetlib.dhp.aggregation.common; import java.io.BufferedOutputStream; +import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; +import org.apache.commons.io.IOUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FSDataInputStream; import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; @@ -25,16 +30,31 @@ public class AggregationUtility { public static final ObjectMapper MAPPER = new ObjectMapper(); - public static void writeTotalSizeOnHDFS(final SparkSession spark, final Long total, final String path) + public static void writeHdfsFile(final Configuration conf, final String content, final String path) throws IOException { - log.info("writing size ({}) info file {}", total, path); - try (FileSystem fs = FileSystem.get(spark.sparkContext().hadoopConfiguration()); + log.info("writing file {}, size {}", path, content.length()); + try (FileSystem fs = FileSystem.get(conf); BufferedOutputStream os = new BufferedOutputStream(fs.create(new Path(path)))) { - os.write(total.toString().getBytes(StandardCharsets.UTF_8)); + os.write(content.getBytes(StandardCharsets.UTF_8)); os.flush(); } + } + public static String readHdfsFile(Configuration conf, String path) throws IOException { + log.info("reading file {}", path); + + try (FileSystem fs = FileSystem.get(conf)) { + final Path p = new Path(path); + if (!fs.exists(p)) { + throw new FileNotFoundException(path); + } + return IOUtils.toString(fs.open(p)); + } + } + + public static T readHdfsFileAs(Configuration conf, String path, Class clazz) throws IOException { + return MAPPER.readValue(readHdfsFile(conf, path), clazz); } public static void saveDataset(final Dataset mdstore, final String targetPath) { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java index fdf3965d6..5839df2d0 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java @@ -28,8 +28,6 @@ import org.dom4j.io.SAXReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.databind.ObjectMapper; - import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; @@ -47,7 +45,7 @@ public class GenerateNativeStoreSparkJob { .toString( GenerateNativeStoreSparkJob.class .getResourceAsStream( - "/eu/dnetlib/dhp/collection/collection_input_parameters.json"))); + "/eu/dnetlib/dhp/collection/generate_native_input_parameters.json"))); parser.parseArgument(args); final String provenanceArgument = parser.get("provenance"); @@ -148,7 +146,9 @@ public class GenerateNativeStoreSparkJob { final Long total = spark.read().load(targetPath).count(); log.info("collected {} records for datasource '{}'", total, provenance.getDatasourceName()); - writeTotalSizeOnHDFS(spark, total, currentVersion.getHdfsPath() + MDSTORE_SIZE_PATH); + writeHdfsFile( + spark.sparkContext().hadoopConfiguration(), total.toString(), + currentVersion.getHdfsPath() + MDSTORE_SIZE_PATH); } public static class MDStoreAggregator extends Aggregator { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java index a0c546858..d0905aade 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java @@ -4,12 +4,11 @@ package eu.dnetlib.dhp.collection.plugin; import java.util.stream.Stream; import eu.dnetlib.dhp.collection.worker.CollectorException; -import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginErrorLogList; +import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginReport; import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; public interface CollectorPlugin { - Stream collect(ApiDescriptor api) throws CollectorException; + Stream collect(ApiDescriptor api, CollectorPluginReport report) throws CollectorException; - CollectorPluginErrorLogList getCollectionErrors(); } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java index ea74919c5..29e12f312 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java @@ -9,15 +9,14 @@ import java.util.Spliterators; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import org.jetbrains.annotations.NotNull; - import com.google.common.base.Splitter; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; import eu.dnetlib.dhp.collection.worker.CollectorException; -import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginErrorLogList; +import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginReport; +import eu.dnetlib.dhp.collection.worker.utils.HttpClientParams; import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; public class OaiCollectorPlugin implements CollectorPlugin { @@ -29,19 +28,15 @@ public class OaiCollectorPlugin implements CollectorPlugin { private OaiIteratorFactory oaiIteratorFactory; - private final CollectorPluginErrorLogList errorLogList = new CollectorPluginErrorLogList(); + private HttpClientParams clientParams; - @Override - public Stream collect(final ApiDescriptor api) throws CollectorException { - try { - return doCollect(api); - } catch (CollectorException e) { - errorLogList.add(e.getMessage()); - throw e; - } + public OaiCollectorPlugin(HttpClientParams clientParams) { + this.clientParams = clientParams; } - private Stream doCollect(ApiDescriptor api) throws CollectorException { + @Override + public Stream collect(final ApiDescriptor api, final CollectorPluginReport report) + throws CollectorException { final String baseUrl = api.getBaseUrl(); final String mdFormat = api.getParams().get(FORMAT_PARAM); final String setParam = api.getParams().get(OAI_SET_PARAM); @@ -79,7 +74,7 @@ public class OaiCollectorPlugin implements CollectorPlugin { .stream() .map( set -> getOaiIteratorFactory() - .newIterator(baseUrl, mdFormat, set, fromDate, untilDate, errorLogList)) + .newIterator(baseUrl, mdFormat, set, fromDate, untilDate, getClientParams(), report)) .iterator(); return StreamSupport @@ -94,8 +89,11 @@ public class OaiCollectorPlugin implements CollectorPlugin { return oaiIteratorFactory; } - @Override - public CollectorPluginErrorLogList getCollectionErrors() { - return errorLogList; + public HttpClientParams getClientParams() { + return clientParams; + } + + public void setClientParams(HttpClientParams clientParams) { + this.clientParams = clientParams; } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java index 9c1ff0663..667e7a3d3 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java @@ -17,7 +17,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.collection.worker.CollectorException; -import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginErrorLogList; +import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginReport; import eu.dnetlib.dhp.collection.worker.utils.HttpConnector2; import eu.dnetlib.dhp.collection.worker.utils.XmlCleaner; @@ -25,6 +25,8 @@ public class OaiIterator implements Iterator { private static final Logger log = LoggerFactory.getLogger(OaiIterator.class); + private final static String REPORT_PREFIX = "oai:"; + private final Queue queue = new PriorityBlockingQueue<>(); private final SAXReader reader = new SAXReader(); @@ -36,7 +38,7 @@ public class OaiIterator implements Iterator { private String token; private boolean started; private final HttpConnector2 httpConnector; - private CollectorPluginErrorLogList errorLogList; + private CollectorPluginReport errorLogList; public OaiIterator( final String baseUrl, @@ -45,7 +47,7 @@ public class OaiIterator implements Iterator { final String fromDate, final String untilDate, final HttpConnector2 httpConnector, - final CollectorPluginErrorLogList errorLogList) { + final CollectorPluginReport errorLogList) { this.baseUrl = baseUrl; this.mdFormat = mdFormat; this.set = set; @@ -111,7 +113,7 @@ public class OaiIterator implements Iterator { return downloadPage(url); } catch (final UnsupportedEncodingException e) { - errorLogList.add(e.getMessage()); + errorLogList.put(e.getClass().getName(), e.getMessage()); throw new CollectorException(e); } } @@ -137,7 +139,7 @@ public class OaiIterator implements Iterator { + "?verb=ListRecords&resumptionToken=" + URLEncoder.encode(resumptionToken, "UTF-8")); } catch (final UnsupportedEncodingException e) { - errorLogList.add(e.getMessage()); + errorLogList.put(e.getClass().getName(), e.getMessage()); throw new CollectorException(e); } } @@ -150,14 +152,14 @@ public class OaiIterator implements Iterator { doc = reader.read(new StringReader(xml)); } catch (final DocumentException e) { log.warn("Error parsing xml, I try to clean it. {}", e.getMessage()); - errorLogList.add(e.getMessage()); + errorLogList.put(e.getClass().getName(), e.getMessage()); final String cleaned = XmlCleaner.cleanAllEntities(xml); try { doc = reader.read(new StringReader(cleaned)); } catch (final DocumentException e1) { final String resumptionToken = extractResumptionToken(xml); if (resumptionToken == null) { - errorLogList.add(e1.getMessage()); + errorLogList.put(e1.getClass().getName(), e1.getMessage()); throw new CollectorException("Error parsing cleaned document:\n" + cleaned, e1); } return resumptionToken; @@ -166,15 +168,15 @@ public class OaiIterator implements Iterator { final Node errorNode = doc.selectSingleNode("/*[local-name()='OAI-PMH']/*[local-name()='error']"); if (errorNode != null) { - final String code = errorNode.valueOf("@code"); - if ("noRecordsMatch".equalsIgnoreCase(code.trim())) { + final String code = errorNode.valueOf("@code").trim(); + if ("noRecordsMatch".equalsIgnoreCase(code)) { final String msg = "noRecordsMatch for oai call : " + url; log.warn(msg); - errorLogList.add(msg); + errorLogList.put(REPORT_PREFIX + code, msg); return null; } else { final String msg = code + " - " + errorNode.getText(); - errorLogList.add(msg); + errorLogList.put(REPORT_PREFIX + "error", msg); throw new CollectorException(msg); } } @@ -186,7 +188,7 @@ public class OaiIterator implements Iterator { return doc.valueOf("//*[local-name()='resumptionToken']"); } - public CollectorPluginErrorLogList getErrorLogList() { + public CollectorPluginReport getErrorLogList() { return errorLogList; } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java index d6759580f..c751a94e7 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java @@ -3,7 +3,8 @@ package eu.dnetlib.dhp.collection.plugin.oai; import java.util.Iterator; -import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginErrorLogList; +import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginReport; +import eu.dnetlib.dhp.collection.worker.utils.HttpClientParams; import eu.dnetlib.dhp.collection.worker.utils.HttpConnector2; public class OaiIteratorFactory { @@ -16,13 +17,15 @@ public class OaiIteratorFactory { final String set, final String fromDate, final String untilDate, - final CollectorPluginErrorLogList errorLogList) { - return new OaiIterator(baseUrl, mdFormat, set, fromDate, untilDate, getHttpConnector(), errorLogList); + final HttpClientParams clientParams, + final CollectorPluginReport errorLogList) { + return new OaiIterator(baseUrl, mdFormat, set, fromDate, untilDate, getHttpConnector(clientParams), + errorLogList); } - private HttpConnector2 getHttpConnector() { + private HttpConnector2 getHttpConnector(HttpClientParams clientParams) { if (httpConnector == null) - httpConnector = new HttpConnector2(); + httpConnector = new HttpConnector2(clientParams); return httpConnector; } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java index 9d82a1ed4..b0efd088c 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java @@ -1,14 +1,13 @@ package eu.dnetlib.dhp.collection.worker; +import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.SEQUENCE_FILE_NAME; +import static eu.dnetlib.dhp.application.ApplicationUtils.*; + import java.io.IOException; -import java.net.URI; import java.util.concurrent.atomic.AtomicInteger; -import eu.dnetlib.dhp.message.Message; -import eu.dnetlib.dhp.message.MessageSender; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.SequenceFile; @@ -16,10 +15,14 @@ import org.apache.hadoop.io.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; -import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginErrorLogList; import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginFactory; +import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginReport; +import eu.dnetlib.dhp.collection.worker.utils.HttpClientParams; +import eu.dnetlib.dhp.collection.worker.utils.UnknownCollectorPluginException; import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; +import eu.dnetlib.dhp.message.MessageSender; public class CollectorWorker { @@ -27,70 +30,71 @@ public class CollectorWorker { private final ApiDescriptor api; - private final String hdfsuri; + private final Configuration conf; - private final String hdfsPath; + private final MDStoreVersion mdStoreVersion; + + private final HttpClientParams clientParams; + + private final CollectorPluginReport report; private final MessageSender messageSender; - public CollectorWorker( final ApiDescriptor api, - final String hdfsuri, - final String hdfsPath, - final MessageSender messageSender) { + final Configuration conf, + final MDStoreVersion mdStoreVersion, + final HttpClientParams clientParams, + final MessageSender messageSender, + final CollectorPluginReport report) { this.api = api; - this.hdfsuri = hdfsuri; - this.hdfsPath = hdfsPath; + this.conf = conf; + this.mdStoreVersion = mdStoreVersion; + this.clientParams = clientParams; this.messageSender = messageSender; + this.report = report; } - public CollectorPluginErrorLogList collect() throws IOException, CollectorException { + public void collect() throws UnknownCollectorPluginException, CollectorException, IOException { - // ====== Init HDFS File System Object - Configuration conf = new Configuration(); - // Set FileSystem URI - conf.set("fs.defaultFS", hdfsuri); - // Because of Maven - conf.set("fs.hdfs.impl", org.apache.hadoop.hdfs.DistributedFileSystem.class.getName()); - conf.set("fs.file.impl", org.apache.hadoop.fs.LocalFileSystem.class.getName()); + final String outputPath = mdStoreVersion.getHdfsPath() + SEQUENCE_FILE_NAME; + log.info("outputPath path is {}", outputPath); - System.setProperty("hadoop.home.dir", "/"); - // Get the filesystem - HDFS - - FileSystem.get(URI.create(hdfsuri), conf); - Path hdfswritepath = new Path(hdfsPath); - - log.info("Created path " + hdfswritepath.toString()); - - final CollectorPlugin plugin = CollectorPluginFactory.getPluginByProtocol(api.getProtocol()); + final CollectorPlugin plugin = CollectorPluginFactory.getPluginByProtocol(clientParams, api.getProtocol()); final AtomicInteger counter = new AtomicInteger(0); try (SequenceFile.Writer writer = SequenceFile .createWriter( conf, - SequenceFile.Writer.file(hdfswritepath), + SequenceFile.Writer.file(new Path(outputPath)), SequenceFile.Writer.keyClass(IntWritable.class), SequenceFile.Writer.valueClass(Text.class))) { final IntWritable key = new IntWritable(counter.get()); final Text value = new Text(); plugin - .collect(api) + .collect(api, report) .forEach( content -> { key.set(counter.getAndIncrement()); - if (counter.get()% 500 == 0) + if (counter.get() % 500 == 0) messageSender.sendMessage(counter.longValue(), null); value.set(content); try { writer.append(key, value); - } catch (IOException e) { + } catch (Throwable e) { + report.put(e.getClass().getName(), e.getMessage()); + log.warn("setting report to failed"); + report.setSuccess(false); throw new RuntimeException(e); } }); + } catch (Throwable e) { + report.put(e.getClass().getName(), e.getMessage()); + log.warn("setting report to failed"); + report.setSuccess(false); } finally { - messageSender.sendMessage(counter.longValue(),counter.longValue()); - return plugin.getCollectionErrors(); + messageSender.sendMessage(counter.longValue(), counter.longValue()); } } + } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java index d8e8a8e49..8f26074c3 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java @@ -6,22 +6,27 @@ import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.*; import static eu.dnetlib.dhp.application.ApplicationUtils.*; import java.io.IOException; +import java.util.Optional; -import eu.dnetlib.dhp.message.Message; -import eu.dnetlib.dhp.message.MessageSender; import org.apache.commons.cli.ParseException; +import org.apache.commons.io.FileSystemUtils; import org.apache.commons.io.IOUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FSDataOutputStream; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.databind.ObjectMapper; - import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; import eu.dnetlib.dhp.aggregation.common.AggregationUtility; import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginErrorLogList; -import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginFactory; +import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginReport; +import eu.dnetlib.dhp.collection.worker.utils.HttpClientParams; +import eu.dnetlib.dhp.collection.worker.utils.UnknownCollectorPluginException; import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; +import eu.dnetlib.dhp.message.MessageSender; /** * CollectorWorkerApplication is the main class responsible to start the metadata collection process, storing the outcomes @@ -35,19 +40,18 @@ public class CollectorWorkerApplication { private static final Logger log = LoggerFactory.getLogger(CollectorWorkerApplication.class); - public static final String COLLECTOR_WORKER_ERRORS = "collectorWorker-errors"; - /** * @param args */ - public static void main(final String[] args) throws ParseException, IOException, CollectorException { + public static void main(final String[] args) + throws ParseException, IOException, UnknownCollectorPluginException, CollectorException { final ArgumentApplicationParser argumentParser = new ArgumentApplicationParser( IOUtils .toString( CollectorWorker.class .getResourceAsStream( - "/eu/dnetlib/dhp/collection/collector_parameter.json"))); + "/eu/dnetlib/dhp/collection/collector_worker_input_parameter.json"))); argumentParser.parseArgument(args); final String hdfsuri = argumentParser.get("namenode"); @@ -65,21 +69,61 @@ public class CollectorWorkerApplication { final String workflowId = argumentParser.get("workflowId"); log.info("workflowId is {}", workflowId); - final MessageSender ms = new MessageSender(dnetMessageManagerURL,workflowId); + final MessageSender ms = new MessageSender(dnetMessageManagerURL, workflowId); final MDStoreVersion currentVersion = MAPPER.readValue(mdStoreVersion, MDStoreVersion.class); - final String hdfsPath = currentVersion.getHdfsPath() + SEQUENCE_FILE_NAME; - log.info("hdfs path is {}", hdfsPath); + + final String reportPath = currentVersion.getHdfsPath() + REPORT_FILE_NAME; + log.info("report path is {}", reportPath); + + final HttpClientParams clientParams = getClientParams(argumentParser); final ApiDescriptor api = MAPPER.readValue(apiDescriptor, ApiDescriptor.class); + final Configuration conf = getHadoopConfiguration(hdfsuri); - final CollectorWorker worker = new CollectorWorker(api, hdfsuri, hdfsPath, ms); - CollectorPluginErrorLogList errors = worker.collect(); - - populateOOZIEEnv(COLLECTOR_WORKER_ERRORS, errors.toString()); - + try (CollectorPluginReport report = new CollectorPluginReport(FileSystem.get(conf), new Path(reportPath))) { + final CollectorWorker worker = new CollectorWorker(api, conf, currentVersion, clientParams, ms, report); + worker.collect(); + report.setSuccess(true); + } catch (Throwable e) { + log.info("got exception {}, ignoring", e.getMessage()); + } } + private static HttpClientParams getClientParams(ArgumentApplicationParser argumentParser) { + final HttpClientParams clientParams = new HttpClientParams(); + clientParams + .setMaxNumberOfRetry( + Optional + .ofNullable(argumentParser.get("maxNumberOfRetry")) + .map(Integer::parseInt) + .orElse(HttpClientParams._maxNumberOfRetry)); + log.info("maxNumberOfRetry is {}", clientParams.getMaxNumberOfRetry()); + clientParams + .setRetryDelay( + Optional + .ofNullable(argumentParser.get("retryDelay")) + .map(Integer::parseInt) + .orElse(HttpClientParams._retryDelay)); + log.info("retryDelay is {}", clientParams.getRetryDelay()); + + clientParams + .setConnectTimeOut( + Optional + .ofNullable(argumentParser.get("connectTimeOut")) + .map(Integer::parseInt) + .orElse(HttpClientParams._connectTimeOut)); + log.info("connectTimeOut is {}", clientParams.getConnectTimeOut()); + + clientParams + .setReadTimeOut( + Optional + .ofNullable(argumentParser.get("readTimeOut")) + .map(Integer::parseInt) + .orElse(HttpClientParams._readTimeOut)); + log.info("readTimeOut is {}", clientParams.getReadTimeOut()); + return clientParams; + } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerReporter.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerReporter.java new file mode 100644 index 000000000..e0e402cfb --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerReporter.java @@ -0,0 +1,69 @@ + +package eu.dnetlib.dhp.collection.worker; + +import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.REPORT_FILE_NAME; +import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.*; +import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.MAPPER; +import static eu.dnetlib.dhp.application.ApplicationUtils.getHadoopConfiguration; +import static eu.dnetlib.dhp.application.ApplicationUtils.populateOOZIEEnv; + +import java.io.IOException; +import java.util.Objects; + +import org.apache.commons.cli.ParseException; +import org.apache.commons.io.IOUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.yarn.webapp.hamlet.Hamlet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; +import eu.dnetlib.dhp.aggregation.common.AggregationUtility; +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginReport; +import eu.dnetlib.dhp.collection.worker.utils.UnknownCollectorPluginException; + +/** + * CollectorWorkerReporter + */ +public class CollectorWorkerReporter { + + private static final Logger log = LoggerFactory.getLogger(CollectorWorkerReporter.class); + + /** + * @param args + */ + public static void main(final String[] args) throws IOException, ParseException, CollectorException { + + final ArgumentApplicationParser argumentParser = new ArgumentApplicationParser( + IOUtils + .toString( + CollectorWorker.class + .getResourceAsStream( + "/eu/dnetlib/dhp/collection/collector_reporter_input_parameter.json"))); + argumentParser.parseArgument(args); + + final String nameNode = argumentParser.get("namenode"); + log.info("nameNode is {}", nameNode); + + final String mdStoreVersion = argumentParser.get("mdStoreVersion"); + log.info("mdStoreVersion is {}", mdStoreVersion); + + final MDStoreVersion currentVersion = MAPPER.readValue(mdStoreVersion, MDStoreVersion.class); + + final String reportPath = currentVersion.getHdfsPath() + REPORT_FILE_NAME; + log.info("report path is {}", reportPath); + + final Configuration conf = getHadoopConfiguration(nameNode); + CollectorPluginReport report = readHdfsFileAs(conf, reportPath, CollectorPluginReport.class); + if (Objects.isNull(report)) { + throw new CollectorException("collection report is NULL"); + } + log.info("report success: {}, size: {}", report.isSuccess(), report.size()); + report.forEach((k, v) -> log.info("{} - {}", k, v)); + if (!report.isSuccess()) { + throw new CollectorException("collection report indicates a failure"); + } + } + +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginErrorLogList.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginErrorLogList.java deleted file mode 100644 index dcaf0ea56..000000000 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginErrorLogList.java +++ /dev/null @@ -1,19 +0,0 @@ - -package eu.dnetlib.dhp.collection.worker.utils; - -import java.util.LinkedList; - -public class CollectorPluginErrorLogList extends LinkedList { - - private static final long serialVersionUID = -6925786561303289704L; - - @Override - public String toString() { - String log = ""; - int index = 0; - for (final String errorMessage : this) { - log += String.format("Retry #%s: %s / ", index++, errorMessage); - } - return log; - } -} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginFactory.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginFactory.java index 7cbcd9b5c..ab7dad077 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginFactory.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginFactory.java @@ -7,14 +7,15 @@ import eu.dnetlib.dhp.collection.worker.CollectorException; public class CollectorPluginFactory { - public static CollectorPlugin getPluginByProtocol(final String protocol) throws CollectorException { + public static CollectorPlugin getPluginByProtocol(final HttpClientParams clientParams, final String protocol) + throws UnknownCollectorPluginException { if (protocol == null) - throw new CollectorException("protocol cannot be null"); + throw new UnknownCollectorPluginException("protocol cannot be null"); switch (protocol.toLowerCase().trim()) { case "oai": - return new OaiCollectorPlugin(); + return new OaiCollectorPlugin(clientParams); default: - throw new CollectorException("UNknown protocol"); + throw new UnknownCollectorPluginException("Unknown protocol"); } } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginReport.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginReport.java new file mode 100644 index 000000000..b7bf539dc --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginReport.java @@ -0,0 +1,64 @@ + +package eu.dnetlib.dhp.collection.worker.utils; + +import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.MAPPER; + +import java.io.Closeable; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Objects; + +import org.apache.commons.io.IOUtils; +import org.apache.hadoop.fs.FSDataOutputStream; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import eu.dnetlib.dhp.application.ApplicationUtils; + +public class CollectorPluginReport extends LinkedHashMap implements Closeable { + + private static final Logger log = LoggerFactory.getLogger(CollectorPluginReport.class); + + @JsonIgnore + private FileSystem fs; + + @JsonIgnore + private Path path; + + @JsonIgnore + private FSDataOutputStream fos; + + public static String SUCCESS = "success"; + + public CollectorPluginReport() { + } + + public CollectorPluginReport(FileSystem fs, Path path) throws IOException { + this.fs = fs; + this.path = path; + + this.fos = fs.create(path); + } + + public Boolean isSuccess() { + return Boolean.valueOf(get(SUCCESS)); + } + + public void setSuccess(Boolean success) { + put(SUCCESS, String.valueOf(success)); + } + + @Override + public void close() throws IOException { + final String data = MAPPER.writeValueAsString(this); + if (Objects.nonNull(fos)) { + log.info("writing report {} to {}", data, path.toString()); + IOUtils.write(data, fos); + ApplicationUtils.populateOOZIEEnv(this); + } + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpClientParams.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpClientParams.java new file mode 100644 index 000000000..e77f3680f --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpClientParams.java @@ -0,0 +1,62 @@ + +package eu.dnetlib.dhp.collection.worker.utils; + +/** + * Bundles the http connection parameters driving the client behaviour. + */ +public class HttpClientParams { + + public static int _maxNumberOfRetry = 3; + public static int _retryDelay = 10; // seconds + public static int _connectTimeOut = 10; // seconds + public static int _readTimeOut = 30; // seconds + + private int maxNumberOfRetry; + private int retryDelay; + private int connectTimeOut; + private int readTimeOut; + + public HttpClientParams() { + this(_maxNumberOfRetry, _retryDelay, _connectTimeOut, _readTimeOut); + } + + public HttpClientParams(int maxNumberOfRetry, int retryDelay, int connectTimeOut, int readTimeOut) { + this.maxNumberOfRetry = maxNumberOfRetry; + this.retryDelay = retryDelay; + this.connectTimeOut = connectTimeOut; + this.readTimeOut = readTimeOut; + } + + public int getMaxNumberOfRetry() { + return maxNumberOfRetry; + } + + public void setMaxNumberOfRetry(int maxNumberOfRetry) { + this.maxNumberOfRetry = maxNumberOfRetry; + } + + public int getRetryDelay() { + return retryDelay; + } + + public void setRetryDelay(int retryDelay) { + this.retryDelay = retryDelay; + } + + public void setConnectTimeOut(int connectTimeOut) { + this.connectTimeOut = connectTimeOut; + } + + public int getConnectTimeOut() { + return connectTimeOut; + } + + public int getReadTimeOut() { + return readTimeOut; + } + + public void setReadTimeOut(int readTimeOut) { + this.readTimeOut = readTimeOut; + } + +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector.java deleted file mode 100644 index 39c2371b9..000000000 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector.java +++ /dev/null @@ -1,218 +0,0 @@ - -package eu.dnetlib.dhp.collection.worker.utils; - -import java.io.IOException; -import java.io.InputStream; -import java.net.*; -import java.util.List; -import java.util.Map; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.math.NumberUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import eu.dnetlib.dhp.collection.worker.CollectorException; - -@Deprecated -public class HttpConnector { - - private static final Logger log = LoggerFactory.getLogger(HttpConnector.class); - - private int maxNumberOfRetry = 6; - private int defaultDelay = 120; // seconds - private int readTimeOut = 120; // seconds - - private String responseType = null; - - private final String userAgent = "Mozilla/5.0 (compatible; OAI; +http://www.openaire.eu)"; - - public HttpConnector() { - CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL)); - } - - /** - * Given the URL returns the content via HTTP GET - * - * @param requestUrl the URL - * @return the content of the downloaded resource - * @throws CollectorException when retrying more than maxNumberOfRetry times - */ - public String getInputSource(final String requestUrl) throws CollectorException { - return attemptDownloadAsString(requestUrl, 1, new CollectorPluginErrorLogList()); - } - - /** - * Given the URL returns the content via HTTP GET - * - * @param requestUrl the URL - * @param errorLogList the list of errors - * @return the content of the downloaded resource - * @throws CollectorException when retrying more than maxNumberOfRetry times - */ - public String getInputSource(final String requestUrl, CollectorPluginErrorLogList errorLogList) - throws CollectorException { - return attemptDownloadAsString(requestUrl, 1, errorLogList); - } - - /** - * Given the URL returns the content as a stream via HTTP GET - * - * @param requestUrl the URL - * @return the content of the downloaded resource as InputStream - * @throws CollectorException when retrying more than maxNumberOfRetry times - */ - public InputStream getInputSourceAsStream(final String requestUrl) throws CollectorException { - return attemptDownload(requestUrl, 1, new CollectorPluginErrorLogList()); - } - - private String attemptDownloadAsString( - final String requestUrl, final int retryNumber, final CollectorPluginErrorLogList errorList) - throws CollectorException { - - log.info("requesting URL [{}]", requestUrl); - try { - final InputStream s = attemptDownload(requestUrl, 1, new CollectorPluginErrorLogList()); - try { - return IOUtils.toString(s); - } catch (final IOException e) { - log.error("error while retrieving from http-connection occurred: {}", requestUrl, e); - Thread.sleep(defaultDelay * 1000); - errorList.add(e.getMessage()); - return attemptDownloadAsString(requestUrl, retryNumber + 1, errorList); - } finally { - IOUtils.closeQuietly(s); - } - } catch (final InterruptedException e) { - throw new CollectorException(e); - } - } - - private InputStream attemptDownload( - final String requestUrl, final int retryNumber, final CollectorPluginErrorLogList errorList) - throws CollectorException { - - if (retryNumber > maxNumberOfRetry) { - throw new CollectorException("Max number of retries exceeded. Cause: \n " + errorList); - } - - log.debug("requesting URL [{}], try {}", requestUrl, retryNumber); - try { - InputStream input = null; - - try { - final HttpURLConnection urlConn = (HttpURLConnection) new URL(requestUrl).openConnection(); - urlConn.setInstanceFollowRedirects(false); - urlConn.setReadTimeout(readTimeOut * 1000); - urlConn.addRequestProperty("User-Agent", userAgent); - - if (log.isDebugEnabled()) { - logHeaderFields(urlConn); - } - - final int retryAfter = obtainRetryAfter(urlConn.getHeaderFields()); - if (retryAfter > 0 && urlConn.getResponseCode() == HttpURLConnection.HTTP_UNAVAILABLE) { - log.warn("waiting and repeating request after {} sec.", retryAfter); - Thread.sleep(retryAfter * 1000); - errorList.add("503 Service Unavailable"); - urlConn.disconnect(); - return attemptDownload(requestUrl, retryNumber + 1, errorList); - } else if (urlConn.getResponseCode() == HttpURLConnection.HTTP_MOVED_PERM - || urlConn.getResponseCode() == HttpURLConnection.HTTP_MOVED_TEMP) { - final String newUrl = obtainNewLocation(urlConn.getHeaderFields()); - log.debug("The requested url has been moved to {}", newUrl); - errorList - .add( - String - .format( - "%s %s. Moved to: %s", - urlConn.getResponseCode(), urlConn.getResponseMessage(), newUrl)); - urlConn.disconnect(); - return attemptDownload(newUrl, retryNumber + 1, errorList); - } else if (urlConn.getResponseCode() != HttpURLConnection.HTTP_OK) { - final String msg = String - .format("HTTP error: %s %s", urlConn.getResponseCode(), urlConn.getResponseMessage()); - log.error(msg); - Thread.sleep(defaultDelay * 1000); - errorList.add(msg); - urlConn.disconnect(); - return attemptDownload(requestUrl, retryNumber + 1, errorList); - } else { - input = urlConn.getInputStream(); - responseType = urlConn.getContentType(); - return input; - } - } catch (final IOException e) { - log.error("error while retrieving from http-connection occurred: {}", requestUrl, e); - Thread.sleep(defaultDelay * 1000); - errorList.add(e.getMessage()); - return attemptDownload(requestUrl, retryNumber + 1, errorList); - } - } catch (final InterruptedException e) { - throw new CollectorException(e); - } - } - - private void logHeaderFields(final HttpURLConnection urlConn) throws IOException { - log.debug("StatusCode: {}", urlConn.getResponseMessage()); - - for (final Map.Entry> e : urlConn.getHeaderFields().entrySet()) { - if (e.getKey() != null) { - for (final String v : e.getValue()) { - log.debug(" key: {} value: {}", e.getKey(), v); - } - } - } - } - - private int obtainRetryAfter(final Map> headerMap) { - for (final String key : headerMap.keySet()) { - if (key != null - && key.toLowerCase().equals("retry-after") - && headerMap.get(key).size() > 0 - && NumberUtils.isNumber(headerMap.get(key).get(0))) { - return Integer.parseInt(headerMap.get(key).get(0)) + 10; - } - } - return -1; - } - - private String obtainNewLocation(final Map> headerMap) - throws CollectorException { - for (final String key : headerMap.keySet()) { - if (key != null && key.toLowerCase().equals("location") && headerMap.get(key).size() > 0) { - return headerMap.get(key).get(0); - } - } - throw new CollectorException( - "The requested url has been MOVED, but 'location' param is MISSING"); - } - - public int getMaxNumberOfRetry() { - return maxNumberOfRetry; - } - - public void setMaxNumberOfRetry(final int maxNumberOfRetry) { - this.maxNumberOfRetry = maxNumberOfRetry; - } - - public int getDefaultDelay() { - return defaultDelay; - } - - public void setDefaultDelay(final int defaultDelay) { - this.defaultDelay = defaultDelay; - } - - public int getReadTimeOut() { - return readTimeOut; - } - - public void setReadTimeOut(final int readTimeOut) { - this.readTimeOut = readTimeOut; - } - - public String getResponseType() { - return responseType; - } -} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector2.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector2.java index b316f34ed..68b1ef8ad 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector2.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector2.java @@ -1,24 +1,17 @@ package eu.dnetlib.dhp.collection.worker.utils; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.*; -import java.security.GeneralSecurityException; -import java.security.cert.X509Certificate; import java.util.List; import java.util.Map; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.math.NumberUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpHeaders; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.collection.worker.CollectorException; @@ -29,162 +22,151 @@ import eu.dnetlib.dhp.collection.worker.CollectorException; */ public class HttpConnector2 { - private static final Log log = LogFactory.getLog(HttpConnector.class); + private static final Logger log = LoggerFactory.getLogger(HttpConnector2.class); - private int maxNumberOfRetry = 6; - private int defaultDelay = 120; // seconds - private int readTimeOut = 120; // seconds + private static final String REPORT_PREFIX = "http:"; + + private HttpClientParams clientParams; private String responseType = null; private String userAgent = "Mozilla/5.0 (compatible; OAI; +http://www.openaire.eu)"; public HttpConnector2() { + this(new HttpClientParams()); + } + + public HttpConnector2(HttpClientParams clientParams) { + this.clientParams = clientParams; CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL)); } /** - * @see HttpConnector2#getInputSource(java.lang.String, eu.dnetlib.dhp.collection.worker.utils.CollectorPluginErrorLogList) - */ - public String getInputSource(final String requestUrl) throws CollectorException { - return attemptDownlaodAsString(requestUrl, 1, new CollectorPluginErrorLogList()); - } - - /** - * @see HttpConnector2#getInputSource(java.lang.String, eu.dnetlib.dhp.collection.worker.utils.CollectorPluginErrorLogList) + * @see HttpConnector2#getInputSource(java.lang.String, CollectorPluginReport) */ public InputStream getInputSourceAsStream(final String requestUrl) throws CollectorException { return IOUtils.toInputStream(getInputSource(requestUrl)); } + /** + * @see HttpConnector2#getInputSource(java.lang.String, CollectorPluginReport) + */ + public String getInputSource(final String requestUrl) throws CollectorException { + return attemptDownloadAsString(requestUrl, 1, new CollectorPluginReport()); + } + /** * Given the URL returns the content via HTTP GET * * @param requestUrl the URL - * @param errorLogList the list of errors + * @param report the list of errors * @return the content of the downloaded resource * @throws CollectorException when retrying more than maxNumberOfRetry times */ - public String getInputSource(final String requestUrl, CollectorPluginErrorLogList errorLogList) + public String getInputSource(final String requestUrl, CollectorPluginReport report) throws CollectorException { - return attemptDownlaodAsString(requestUrl, 1, errorLogList); + return attemptDownloadAsString(requestUrl, 1, report); } - private String attemptDownlaodAsString(final String requestUrl, final int retryNumber, - final CollectorPluginErrorLogList errorList) - throws CollectorException { - try { - InputStream s = attemptDownload(requestUrl, 1, new CollectorPluginErrorLogList()); - try { - return IOUtils.toString(s); - } catch (IOException e) { - log.error("error while retrieving from http-connection occured: " + requestUrl, e); - Thread.sleep(defaultDelay * 1000); - errorList.add(e.getMessage()); - return attemptDownlaodAsString(requestUrl, retryNumber + 1, errorList); - } finally { - IOUtils.closeQuietly(s); - } - } catch (InterruptedException e) { + private String attemptDownloadAsString(final String requestUrl, final int retryNumber, + final CollectorPluginReport report) throws CollectorException { + + try (InputStream s = attemptDownload(requestUrl, retryNumber, report)) { + return IOUtils.toString(s); + } catch (IOException e) { + log.error(e.getMessage(), e); throw new CollectorException(e); } } private InputStream attemptDownload(final String requestUrl, final int retryNumber, - final CollectorPluginErrorLogList errorList) - throws CollectorException { + final CollectorPluginReport report) throws CollectorException, IOException { - if (retryNumber > maxNumberOfRetry) { - throw new CollectorException("Max number of retries exceeded. Cause: \n " + errorList); + if (retryNumber > getClientParams().getMaxNumberOfRetry()) { + throw new CollectorException("Max number of retries exceeded. Cause: \n " + report); } - log.debug("Downloading " + requestUrl + " - try: " + retryNumber); + log.info("Downloading attempt {} [{}]", retryNumber, requestUrl); + + InputStream input = null; + try { - InputStream input = null; + final HttpURLConnection urlConn = (HttpURLConnection) new URL(requestUrl).openConnection(); + urlConn.setInstanceFollowRedirects(false); + urlConn.setReadTimeout(getClientParams().getReadTimeOut() * 1000); + urlConn.setConnectTimeout(getClientParams().getConnectTimeOut() * 1000); + urlConn.addRequestProperty(HttpHeaders.USER_AGENT, userAgent); - try { - final HttpURLConnection urlConn = (HttpURLConnection) new URL(requestUrl).openConnection(); - urlConn.setInstanceFollowRedirects(false); - urlConn.setReadTimeout(readTimeOut * 1000); - urlConn.addRequestProperty("User-Agent", userAgent); - - if (log.isDebugEnabled()) { - logHeaderFields(urlConn); - } - - int retryAfter = obtainRetryAfter(urlConn.getHeaderFields()); - if (is2xx(urlConn.getResponseCode())) { - input = urlConn.getInputStream(); - responseType = urlConn.getContentType(); - return input; - } - if (is3xx(urlConn.getResponseCode())) { - // REDIRECTS - final String newUrl = obtainNewLocation(urlConn.getHeaderFields()); - log.debug(String.format("The requested url %s has been moved to %s", requestUrl, newUrl)); - errorList - .add( - String - .format( - "%s %s %s. Moved to: %s", requestUrl, urlConn.getResponseCode(), - urlConn.getResponseMessage(), newUrl)); - urlConn.disconnect(); - if (retryAfter > 0) - Thread.sleep(retryAfter * 1000); - return attemptDownload(newUrl, retryNumber + 1, errorList); - } - if (is4xx(urlConn.getResponseCode())) { - // CLIENT ERROR, DO NOT RETRY - errorList - .add( - String - .format( - "%s error %s: %s", requestUrl, urlConn.getResponseCode(), - urlConn.getResponseMessage())); - throw new CollectorException("4xx error: request will not be repeated. " + errorList); - } - if (is5xx(urlConn.getResponseCode())) { - // SERVER SIDE ERRORS RETRY ONLY on 503 - switch (urlConn.getResponseCode()) { - case HttpURLConnection.HTTP_UNAVAILABLE: - if (retryAfter > 0) { - log - .warn( - requestUrl + " - waiting and repeating request after suggested retry-after " - + retryAfter + " sec."); - Thread.sleep(retryAfter * 1000); - } else { - log - .warn( - requestUrl + " - waiting and repeating request after default delay of " - + defaultDelay + " sec."); - Thread.sleep(defaultDelay * 1000); - } - errorList.add(requestUrl + " 503 Service Unavailable"); - urlConn.disconnect(); - return attemptDownload(requestUrl, retryNumber + 1, errorList); - default: - errorList - .add( - String - .format( - "%s Error %s: %s", requestUrl, urlConn.getResponseCode(), - urlConn.getResponseMessage())); - throw new CollectorException(urlConn.getResponseCode() + " error " + errorList); - } - } - throw new CollectorException( - String.format("Unexpected status code: %s error %s", urlConn.getResponseCode(), errorList)); - } catch (MalformedURLException | NoRouteToHostException e) { - errorList.add(String.format("Error: %s for request url: %s", e.getCause(), requestUrl)); - throw new CollectorException(e + "error " + errorList); - } catch (IOException e) { - Thread.sleep(defaultDelay * 1000); - errorList.add(requestUrl + " " + e.getMessage()); - return attemptDownload(requestUrl, retryNumber + 1, errorList); + if (log.isDebugEnabled()) { + logHeaderFields(urlConn); } - } catch (InterruptedException e) { - throw new CollectorException(e); + + int retryAfter = obtainRetryAfter(urlConn.getHeaderFields()); + if (is2xx(urlConn.getResponseCode())) { + input = urlConn.getInputStream(); + responseType = urlConn.getContentType(); + return input; + } + if (is3xx(urlConn.getResponseCode())) { + // REDIRECTS + final String newUrl = obtainNewLocation(urlConn.getHeaderFields()); + log.info(String.format("The requested url has been moved to %s", newUrl)); + report + .put( + REPORT_PREFIX + urlConn.getResponseCode(), + String.format("Moved to: %s", newUrl)); + urlConn.disconnect(); + if (retryAfter > 0) { + backoffAndSleep(retryAfter); + } + return attemptDownload(newUrl, retryNumber + 1, report); + } + if (is4xx(urlConn.getResponseCode())) { + // CLIENT ERROR, DO NOT RETRY + report + .put( + REPORT_PREFIX + urlConn.getResponseCode(), + String + .format( + "%s error: %s", requestUrl, urlConn.getResponseMessage())); + throw new CollectorException("4xx error: request will not be repeated. " + report); + } + if (is5xx(urlConn.getResponseCode())) { + // SERVER SIDE ERRORS RETRY ONLY on 503 + switch (urlConn.getResponseCode()) { + case HttpURLConnection.HTTP_UNAVAILABLE: + if (retryAfter > 0) { + log + .warn( + requestUrl + " - waiting and repeating request after suggested retry-after " + + retryAfter + " sec."); + backoffAndSleep(retryAfter * 1000); + } else { + log + .warn( + requestUrl + " - waiting and repeating request after default delay of " + + getClientParams().getRetryDelay() + " sec."); + backoffAndSleep(retryNumber * getClientParams().getRetryDelay() * 1000); + } + report.put(REPORT_PREFIX + urlConn.getResponseCode(), requestUrl); + urlConn.disconnect(); + return attemptDownload(requestUrl, retryNumber + 1, report); + default: + report + .put( + REPORT_PREFIX + urlConn.getResponseCode(), + String + .format( + "%s Error: %s", requestUrl, urlConn.getResponseMessage())); + throw new CollectorException(urlConn.getResponseCode() + " error " + report); + } + } + throw new CollectorException( + String.format("Unexpected status code: %s error %s", urlConn.getResponseCode(), report)); + } catch (MalformedURLException | SocketException | UnknownHostException e) { + log.error(e.getMessage(), e); + report.put(e.getClass().getName(), e.getMessage()); + throw new CollectorException(e.getMessage(), e); } } @@ -200,12 +182,21 @@ public class HttpConnector2 { } } + private void backoffAndSleep(int sleepTime) throws CollectorException { + log.info("I'm going to sleep for {}ms", sleepTime); + try { + Thread.sleep(sleepTime); + } catch (InterruptedException e) { + log.error(e.getMessage(), e); + throw new CollectorException(e); + } + } + private int obtainRetryAfter(final Map> headerMap) { for (String key : headerMap.keySet()) { - if ((key != null) && key.toLowerCase().equals("retry-after") && (headerMap.get(key).size() > 0) + if ((key != null) && key.equalsIgnoreCase(HttpHeaders.RETRY_AFTER) && (headerMap.get(key).size() > 0) && NumberUtils.isCreatable(headerMap.get(key).get(0))) { - return Integer - .parseInt(headerMap.get(key).get(0)) + 10; + return Integer.parseInt(headerMap.get(key).get(0)) + 10; } } return -1; @@ -213,44 +204,13 @@ public class HttpConnector2 { private String obtainNewLocation(final Map> headerMap) throws CollectorException { for (String key : headerMap.keySet()) { - if ((key != null) && key.toLowerCase().equals("location") && (headerMap.get(key).size() > 0)) { + if ((key != null) && key.equalsIgnoreCase(HttpHeaders.LOCATION) && (headerMap.get(key).size() > 0)) { return headerMap.get(key).get(0); } } throw new CollectorException("The requested url has been MOVED, but 'location' param is MISSING"); } - /** - * register for https scheme; this is a workaround and not intended for the use in trusted environments - */ - public void initTrustManager() { - final X509TrustManager tm = new X509TrustManager() { - - @Override - public void checkClientTrusted(final X509Certificate[] xcs, final String string) { - } - - @Override - public void checkServerTrusted(final X509Certificate[] xcs, final String string) { - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return null; - } - }; - try { - final SSLContext ctx = SSLContext.getInstance("TLS"); - ctx.init(null, new TrustManager[] { - tm - }, null); - HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory()); - } catch (GeneralSecurityException e) { - log.fatal(e); - throw new IllegalStateException(e); - } - } - private boolean is2xx(final int statusCode) { return statusCode >= 200 && statusCode <= 299; } @@ -267,32 +227,15 @@ public class HttpConnector2 { return statusCode >= 500 && statusCode <= 599; } - public int getMaxNumberOfRetry() { - return maxNumberOfRetry; - } - - public void setMaxNumberOfRetry(final int maxNumberOfRetry) { - this.maxNumberOfRetry = maxNumberOfRetry; - } - - public int getDefaultDelay() { - return defaultDelay; - } - - public void setDefaultDelay(final int defaultDelay) { - this.defaultDelay = defaultDelay; - } - - public int getReadTimeOut() { - return readTimeOut; - } - - public void setReadTimeOut(final int readTimeOut) { - this.readTimeOut = readTimeOut; - } - public String getResponseType() { return responseType; } + public HttpClientParams getClientParams() { + return clientParams; + } + + public void setClientParams(HttpClientParams clientParams) { + this.clientParams = clientParams; + } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/UnknownCollectorPluginException.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/UnknownCollectorPluginException.java new file mode 100644 index 000000000..c55d485e2 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/UnknownCollectorPluginException.java @@ -0,0 +1,32 @@ + +package eu.dnetlib.dhp.collection.worker.utils; + +public class UnknownCollectorPluginException extends Exception { + + /** */ + private static final long serialVersionUID = -290723075076039757L; + + public UnknownCollectorPluginException() { + super(); + } + + public UnknownCollectorPluginException( + final String message, + final Throwable cause, + final boolean enableSuppression, + final boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public UnknownCollectorPluginException(final String message, final Throwable cause) { + super(message, cause); + } + + public UnknownCollectorPluginException(final String message) { + super(message); + } + + public UnknownCollectorPluginException(final Throwable cause) { + super(cause); + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java index e1b1b849c..b735ecb1f 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java @@ -105,7 +105,8 @@ public class TransformSparkJobNode { log.info("Total item " + ct.getTotalItems().count()); log.info("Transformation Error item " + ct.getErrorItems().count()); - writeTotalSizeOnHDFS(spark, mdstore.count(), outputBasePath + MDSTORE_SIZE_PATH); + writeHdfsFile( + spark.sparkContext().hadoopConfiguration(), "" + mdstore.count(), outputBasePath + MDSTORE_SIZE_PATH); } } diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_reporter_input_parameter.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_reporter_input_parameter.json new file mode 100644 index 000000000..ef65cc389 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_reporter_input_parameter.json @@ -0,0 +1,14 @@ +[ + { + "paramName": "n", + "paramLongName": "namenode", + "paramDescription": "the Name Node URI", + "paramRequired": true + }, + { + "paramName": "mv", + "paramLongName": "mdStoreVersion", + "paramDescription": "the MDStore Version bean", + "paramRequired": true + } +] diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_parameter.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_worker_input_parameter.json similarity index 52% rename from dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_parameter.json rename to dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_worker_input_parameter.json index 6ccba468a..f3eaf2d71 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_parameter.json +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_worker_input_parameter.json @@ -23,12 +23,34 @@ "paramDescription": "the End point URL to send Messages", "paramRequired": true }, - - { "paramName": "w", "paramLongName": "workflowId", "paramDescription": "the identifier of the dnet Workflow", "paramRequired": true + }, + { + "paramName": "mr", + "paramLongName": "maxNumberOfRetry", + "paramDescription": "the maximum number of admitted connection retries", + "paramRequired": false + }, + { + "paramName": "rd", + "paramLongName": "retryDelay", + "paramDescription": "the delay (ms) between retries", + "paramRequired": false + }, + { + "paramName": "ct", + "paramLongName": "connectTimeOut", + "paramDescription": "the maximum allowed time (ms) to connect to the remote host", + "paramRequired": false + }, + { + "paramName": "rt", + "paramLongName": "readTimeOut", + "paramDescription": "the maximum allowed time (ms) to receive content from the remote host", + "paramRequired": false } ] \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collection_input_parameters.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/generate_native_input_parameters.json similarity index 100% rename from dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collection_input_parameters.json rename to dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/generate_native_input_parameters.json diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml index b74ef6b61..e7f6b9201 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml @@ -94,9 +94,22 @@ --workflowId${workflowId} --dnetMessageManagerURL${dnetMessageManagerURL} --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} - + --maxNumberOfRetry${maxNumberOfRetry} + --retryDelay${retryDelay} + --connectTimeOut${connectTimeOut} + --readTimeOut${readTimeOut} + + + + + + + eu.dnetlib.dhp.collection.worker.CollectorWorkerReporter + --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} + --namenode${nameNode} + diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java deleted file mode 100644 index 103e11c33..000000000 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/httpconnector/HttpConnectorTest.java +++ /dev/null @@ -1,44 +0,0 @@ - -package eu.dnetlib.dhp.actionmanager.project.httpconnector; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.ssl.SSLContextBuilder; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import eu.dnetlib.dhp.collection.worker.CollectorException; -import eu.dnetlib.dhp.collection.worker.utils.HttpConnector2; - -@Disabled -public class HttpConnectorTest { - - private static final Log log = LogFactory.getLog(HttpConnectorTest.class); - private static HttpConnector2 connector; - - private static final String URL = "http://cordis.europa.eu/data/reference/cordisref-H2020topics.xlsx"; - private static final String URL_MISCONFIGURED_SERVER = "https://www.alexandria.unisg.ch/cgi/oai2?verb=Identify"; - private static final String URL_GOODSNI_SERVER = "https://air.unimi.it/oai/openaire?verb=Identify"; - - private static final SSLContextBuilder sslContextBuilder = new SSLContextBuilder(); - private static SSLConnectionSocketFactory sslSocketFactory; - - @BeforeAll - public static void setUp() { - connector = new HttpConnector2(); - } - - @Test - - public void testGetInputSource() throws CollectorException { - System.out.println(connector.getInputSource(URL)); - } - - @Test - public void testGoodServers() throws CollectorException { - System.out.println(connector.getInputSource(URL_GOODSNI_SERVER)); - } - -} diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/CollectorWorkerApplicationTests.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/CollectorWorkerApplicationTests.java index 84cad8e19..65c2833eb 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/CollectorWorkerApplicationTests.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/CollectorWorkerApplicationTests.java @@ -13,6 +13,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.collection.worker.CollectorWorker; import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginFactory; +import eu.dnetlib.dhp.collection.worker.utils.HttpClientParams; import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; @Disabled @@ -21,8 +22,9 @@ public class CollectorWorkerApplicationTests { @Test public void testFindPlugin() throws Exception { final CollectorPluginFactory collectorPluginEnumerator = new CollectorPluginFactory(); - assertNotNull(collectorPluginEnumerator.getPluginByProtocol("oai")); - assertNotNull(collectorPluginEnumerator.getPluginByProtocol("OAI")); + final HttpClientParams clientParams = new HttpClientParams(); + assertNotNull(collectorPluginEnumerator.getPluginByProtocol(clientParams, "oai")); + assertNotNull(collectorPluginEnumerator.getPluginByProtocol(clientParams, "OAI")); } @Test diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/utils/CollectorPluginReportTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/utils/CollectorPluginReportTest.java new file mode 100644 index 000000000..69376d5eb --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/utils/CollectorPluginReportTest.java @@ -0,0 +1,29 @@ + +package eu.dnetlib.dhp.collector.worker.utils; + +import java.io.IOException; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import eu.dnetlib.dhp.aggregation.common.AggregationUtility; +import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginReport; + +public class CollectorPluginReportTest { + + @Test + public void testSerialize() throws IOException { + CollectorPluginReport r1 = new CollectorPluginReport(); + r1.put("a", "b"); + r1.setSuccess(true); + + String s = AggregationUtility.MAPPER.writeValueAsString(r1); + + Assertions.assertNotNull(s); + + CollectorPluginReport r2 = AggregationUtility.MAPPER.readValue(s, CollectorPluginReport.class); + + Assertions.assertTrue(r2.isSuccess(), "should be true"); + } + +} From 40df0f987ded40ab983742e3b090afbb5611d0f5 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Sat, 6 Feb 2021 20:12:00 +0100 Subject: [PATCH 102/445] better logging, WIP: collectorWorker error reporting; common functions moved in DHPUtils --- .../dhp/application/ApplicationUtils.java | 30 ------- .../java/eu/dnetlib/dhp/utils/DHPUtils.java | 85 ++++++++++++++++++- .../actionmanager/project/utils/ReadCSV.java | 2 +- .../project/utils/ReadExcel.java | 2 +- .../common/AggregationUtility.java | 69 --------------- .../mdstore/MDStoreActionNode.java | 37 +++----- .../GenerateNativeStoreSparkJob.java | 2 +- .../collection/plugin/CollectorPlugin.java | 2 +- .../plugin/oai/OaiCollectorPlugin.java | 4 +- .../collection/plugin/oai/OaiIterator.java | 6 +- .../plugin/oai/OaiIteratorFactory.java | 6 +- .../{utils => }/CollectorPluginFactory.java | 3 +- .../{utils => }/CollectorPluginReport.java | 8 +- .../collection/worker/CollectorWorker.java | 5 -- .../worker/CollectorWorkerApplication.java | 10 +-- .../worker/CollectorWorkerReporter.java | 9 +- .../worker/{utils => }/HttpClientParams.java | 2 +- .../worker/{utils => }/HttpConnector2.java | 16 +++- .../UnknownCollectorPluginException.java | 2 +- .../worker/{utils => }/XmlCleaner.java | 2 +- .../transformation/TransformSparkJobNode.java | 2 +- .../project/EXCELParserTest.java | 2 +- .../CollectorWorkerApplicationTests.java | 8 +- .../utils/CollectorPluginReportTest.java | 9 +- 24 files changed, 138 insertions(+), 185 deletions(-) delete mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java rename dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/{utils => }/CollectorPluginFactory.java (84%) rename dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/{utils => }/CollectorPluginReport.java (86%) rename dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/{utils => }/HttpClientParams.java (96%) rename dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/{utils => }/HttpConnector2.java (93%) rename dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/{utils => }/UnknownCollectorPluginException.java (93%) rename dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/{utils => }/XmlCleaner.java (99%) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/application/ApplicationUtils.java b/dhp-common/src/main/java/eu/dnetlib/dhp/application/ApplicationUtils.java index c78fb1b1f..c53b83561 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/application/ApplicationUtils.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/application/ApplicationUtils.java @@ -11,34 +11,4 @@ import com.google.common.collect.Maps; public class ApplicationUtils { - public static Configuration getHadoopConfiguration(String nameNode) { - // ====== Init HDFS File System Object - Configuration conf = new Configuration(); - // Set FileSystem URI - conf.set("fs.defaultFS", nameNode); - // Because of Maven - conf.set("fs.hdfs.impl", org.apache.hadoop.hdfs.DistributedFileSystem.class.getName()); - conf.set("fs.file.impl", org.apache.hadoop.fs.LocalFileSystem.class.getName()); - - System.setProperty("hadoop.home.dir", "/"); - return conf; - } - - public static void populateOOZIEEnv(final Map report) throws IOException { - File file = new File(System.getProperty("oozie.action.output.properties")); - Properties props = new Properties(); - report.forEach((k, v) -> props.setProperty(k, v)); - - try(OutputStream os = new FileOutputStream(file)) { - props.store(os, ""); - } - } - - public static void populateOOZIEEnv(final String paramName, String value) throws IOException { - Map report = Maps.newHashMap(); - report.put(paramName, value); - - populateOOZIEEnv(report); - } - } diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/utils/DHPUtils.java b/dhp-common/src/main/java/eu/dnetlib/dhp/utils/DHPUtils.java index 8872174a5..8d760a2cd 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/utils/DHPUtils.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/utils/DHPUtils.java @@ -1,18 +1,29 @@ package eu.dnetlib.dhp.utils; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.*; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.List; +import java.util.Map; +import java.util.Properties; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64OutputStream; import org.apache.commons.codec.binary.Hex; +import org.apache.commons.io.IOUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.SaveMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Maps; import com.jayway.jsonpath.JsonPath; import net.minidev.json.JSONArray; @@ -21,6 +32,8 @@ import scala.collection.Seq; public class DHPUtils { + private static final Logger log = LoggerFactory.getLogger(DHPUtils.class); + public static Seq toSeq(List list) { return JavaConverters.asScalaIteratorConverter(list.iterator()).asScala().toSeq(); } @@ -79,4 +92,72 @@ public class DHPUtils { return ""; } } + + public static final ObjectMapper MAPPER = new ObjectMapper(); + + public static void writeHdfsFile(final Configuration conf, final String content, final String path) + throws IOException { + + log.info("writing file {}, size {}", path, content.length()); + try (FileSystem fs = FileSystem.get(conf); + BufferedOutputStream os = new BufferedOutputStream(fs.create(new Path(path)))) { + os.write(content.getBytes(StandardCharsets.UTF_8)); + os.flush(); + } + } + + public static String readHdfsFile(Configuration conf, String path) throws IOException { + log.info("reading file {}", path); + + try (FileSystem fs = FileSystem.get(conf)) { + final Path p = new Path(path); + if (!fs.exists(p)) { + throw new FileNotFoundException(path); + } + return IOUtils.toString(fs.open(p)); + } + } + + public static T readHdfsFileAs(Configuration conf, String path, Class clazz) throws IOException { + return MAPPER.readValue(readHdfsFile(conf, path), clazz); + } + + public static void saveDataset(final Dataset mdstore, final String targetPath) { + log.info("saving dataset in: {}", targetPath); + mdstore + .write() + .mode(SaveMode.Overwrite) + .format("parquet") + .save(targetPath); + } + + public static Configuration getHadoopConfiguration(String nameNode) { + // ====== Init HDFS File System Object + Configuration conf = new Configuration(); + // Set FileSystem URI + conf.set("fs.defaultFS", nameNode); + // Because of Maven + conf.set("fs.hdfs.impl", org.apache.hadoop.hdfs.DistributedFileSystem.class.getName()); + conf.set("fs.file.impl", org.apache.hadoop.fs.LocalFileSystem.class.getName()); + + System.setProperty("hadoop.home.dir", "/"); + return conf; + } + + public static void populateOOZIEEnv(final Map report) throws IOException { + File file = new File(System.getProperty("oozie.action.output.properties")); + Properties props = new Properties(); + report.forEach((k, v) -> props.setProperty(k, v)); + + try (OutputStream os = new FileOutputStream(file)) { + props.store(os, ""); + } + } + + public static void populateOOZIEEnv(final String paramName, String value) throws IOException { + Map report = Maps.newHashMap(); + report.put(paramName, value); + + populateOOZIEEnv(report); + } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java index 1b9e070fe..3f64eb953 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java @@ -18,7 +18,7 @@ import org.apache.hadoop.fs.Path; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.collection.worker.utils.HttpConnector2; +import eu.dnetlib.dhp.collection.worker.HttpConnector2; /** * Applies the parsing of a csv file and writes the Serialization of it in hdfs diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java index 2ad3f5b34..c661909b0 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java @@ -15,7 +15,7 @@ import org.apache.hadoop.fs.Path; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.collection.worker.utils.HttpConnector2; +import eu.dnetlib.dhp.collection.worker.HttpConnector2; /** * Applies the parsing of an excel file and writes the Serialization of it in hdfs diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java deleted file mode 100644 index 8dad5bb81..000000000 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationUtility.java +++ /dev/null @@ -1,69 +0,0 @@ - -package eu.dnetlib.dhp.aggregation.common; - -import java.io.BufferedOutputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; - -import org.apache.commons.io.IOUtils; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.FSDataInputStream; -import org.apache.hadoop.fs.FSDataOutputStream; -import org.apache.hadoop.fs.FileSystem; -import org.apache.hadoop.fs.Path; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.SaveMode; -import org.apache.spark.sql.SparkSession; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import eu.dnetlib.dhp.collection.GenerateNativeStoreSparkJob; -import eu.dnetlib.dhp.model.mdstore.MetadataRecord; - -public class AggregationUtility { - - private static final Logger log = LoggerFactory.getLogger(AggregationUtility.class); - - public static final ObjectMapper MAPPER = new ObjectMapper(); - - public static void writeHdfsFile(final Configuration conf, final String content, final String path) - throws IOException { - - log.info("writing file {}, size {}", path, content.length()); - try (FileSystem fs = FileSystem.get(conf); - BufferedOutputStream os = new BufferedOutputStream(fs.create(new Path(path)))) { - os.write(content.getBytes(StandardCharsets.UTF_8)); - os.flush(); - } - } - - public static String readHdfsFile(Configuration conf, String path) throws IOException { - log.info("reading file {}", path); - - try (FileSystem fs = FileSystem.get(conf)) { - final Path p = new Path(path); - if (!fs.exists(p)) { - throw new FileNotFoundException(path); - } - return IOUtils.toString(fs.open(p)); - } - } - - public static T readHdfsFileAs(Configuration conf, String path, Class clazz) throws IOException { - return MAPPER.readValue(readHdfsFile(conf, path), clazz); - } - - public static void saveDataset(final Dataset mdstore, final String targetPath) { - log.info("saving dataset in: {}", targetPath); - mdstore - .write() - .mode(SaveMode.Overwrite) - .format("parquet") - .save(targetPath); - } - -} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java index 3e471cfc8..9a47a1d66 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java @@ -1,18 +1,14 @@ package eu.dnetlib.dhp.aggregation.mdstore; -import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.*; +import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.*; import static eu.dnetlib.dhp.application.ApplicationUtils.*; +import static eu.dnetlib.dhp.utils.DHPUtils.*; -import java.io.File; -import java.io.FileOutputStream; -import java.io.OutputStream; import java.net.URI; -import java.util.Properties; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FSDataInputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; @@ -80,29 +76,20 @@ public class MDStoreActionNode { throw new IllegalArgumentException( "invalid MDStoreVersion value current is " + mdStoreVersion_params); } + Path hdfstoreSizepath = new Path(mdStoreVersion.getHdfsPath() + MDSTORE_SIZE_PATH); - Configuration conf = new Configuration(); - // Set FileSystem URI - conf.set("fs.defaultFS", hdfsuri); - // Because of Maven - conf.set("fs.hdfs.impl", org.apache.hadoop.hdfs.DistributedFileSystem.class.getName()); - conf.set("fs.file.impl", org.apache.hadoop.fs.LocalFileSystem.class.getName()); + try ( + FileSystem fs = FileSystem.get(URI.create(hdfsuri), getHadoopConfiguration(hdfsuri)); + FSDataInputStream inputStream = fs.open(hdfstoreSizepath)) { - System.setProperty("hadoop.home.dir", "/"); - // Get the filesystem - HDFS - FileSystem fs = FileSystem.get(URI.create(hdfsuri), conf); + final Long mdStoreSize = Long.parseLong(IOUtils.toString(inputStream)); - Path hdfstoreSizepath = new Path(mdStoreVersion.getHdfsPath() + "/size"); + fs.create(hdfstoreSizepath); + DNetRestClient + .doGET( + String.format(COMMIT_VERSION_URL, mdStoreManagerURI, mdStoreVersion.getId(), mdStoreSize)); + } - FSDataInputStream inputStream = fs.open(hdfstoreSizepath); - - final Long mdStoreSize = Long.parseLong(IOUtils.toString(inputStream)); - - inputStream.close(); - fs.create(hdfstoreSizepath); - - DNetRestClient - .doGET(String.format(COMMIT_VERSION_URL, mdStoreManagerURI, mdStoreVersion.getId(), mdStoreSize)); break; } case ROLLBACK: { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java index 5839df2d0..5c24bb7ec 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java @@ -2,8 +2,8 @@ package eu.dnetlib.dhp.collection; import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.*; -import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.*; import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; +import static eu.dnetlib.dhp.utils.DHPUtils.*; import java.io.ByteArrayInputStream; import java.io.IOException; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java index d0905aade..614aa4e69 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java @@ -4,7 +4,7 @@ package eu.dnetlib.dhp.collection.plugin; import java.util.stream.Stream; import eu.dnetlib.dhp.collection.worker.CollectorException; -import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginReport; +import eu.dnetlib.dhp.collection.worker.CollectorPluginReport; import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; public interface CollectorPlugin { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java index 29e12f312..7ec2f09be 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java @@ -15,8 +15,8 @@ import com.google.common.collect.Lists; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; import eu.dnetlib.dhp.collection.worker.CollectorException; -import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginReport; -import eu.dnetlib.dhp.collection.worker.utils.HttpClientParams; +import eu.dnetlib.dhp.collection.worker.CollectorPluginReport; +import eu.dnetlib.dhp.collection.worker.HttpClientParams; import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; public class OaiCollectorPlugin implements CollectorPlugin { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java index 667e7a3d3..8d913b68f 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java @@ -17,9 +17,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.collection.worker.CollectorException; -import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginReport; -import eu.dnetlib.dhp.collection.worker.utils.HttpConnector2; -import eu.dnetlib.dhp.collection.worker.utils.XmlCleaner; +import eu.dnetlib.dhp.collection.worker.CollectorPluginReport; +import eu.dnetlib.dhp.collection.worker.HttpConnector2; +import eu.dnetlib.dhp.collection.worker.XmlCleaner; public class OaiIterator implements Iterator { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java index c751a94e7..f63fa37a1 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java @@ -3,9 +3,9 @@ package eu.dnetlib.dhp.collection.plugin.oai; import java.util.Iterator; -import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginReport; -import eu.dnetlib.dhp.collection.worker.utils.HttpClientParams; -import eu.dnetlib.dhp.collection.worker.utils.HttpConnector2; +import eu.dnetlib.dhp.collection.worker.CollectorPluginReport; +import eu.dnetlib.dhp.collection.worker.HttpClientParams; +import eu.dnetlib.dhp.collection.worker.HttpConnector2; public class OaiIteratorFactory { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginFactory.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorPluginFactory.java similarity index 84% rename from dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginFactory.java rename to dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorPluginFactory.java index ab7dad077..9668098f0 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginFactory.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorPluginFactory.java @@ -1,9 +1,8 @@ -package eu.dnetlib.dhp.collection.worker.utils; +package eu.dnetlib.dhp.collection.worker; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; import eu.dnetlib.dhp.collection.plugin.oai.OaiCollectorPlugin; -import eu.dnetlib.dhp.collection.worker.CollectorException; public class CollectorPluginFactory { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginReport.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorPluginReport.java similarity index 86% rename from dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginReport.java rename to dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorPluginReport.java index b7bf539dc..2da6ac8f9 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/CollectorPluginReport.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorPluginReport.java @@ -1,7 +1,7 @@ -package eu.dnetlib.dhp.collection.worker.utils; +package eu.dnetlib.dhp.collection.worker; -import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.MAPPER; +import static eu.dnetlib.dhp.utils.DHPUtils.*; import java.io.Closeable; import java.io.IOException; @@ -45,7 +45,7 @@ public class CollectorPluginReport extends LinkedHashMap impleme } public Boolean isSuccess() { - return Boolean.valueOf(get(SUCCESS)); + return containsKey(SUCCESS) && Boolean.valueOf(get(SUCCESS)); } public void setSuccess(Boolean success) { @@ -58,7 +58,7 @@ public class CollectorPluginReport extends LinkedHashMap impleme if (Objects.nonNull(fos)) { log.info("writing report {} to {}", data, path.toString()); IOUtils.write(data, fos); - ApplicationUtils.populateOOZIEEnv(this); + populateOOZIEEnv(this); } } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java index b0efd088c..71dee0d03 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java @@ -2,7 +2,6 @@ package eu.dnetlib.dhp.collection.worker; import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.SEQUENCE_FILE_NAME; -import static eu.dnetlib.dhp.application.ApplicationUtils.*; import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; @@ -17,10 +16,6 @@ import org.slf4j.LoggerFactory; import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; -import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginFactory; -import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginReport; -import eu.dnetlib.dhp.collection.worker.utils.HttpClientParams; -import eu.dnetlib.dhp.collection.worker.utils.UnknownCollectorPluginException; import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; import eu.dnetlib.dhp.message.MessageSender; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java index 8f26074c3..a6c254d42 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java @@ -2,29 +2,21 @@ package eu.dnetlib.dhp.collection.worker; import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.*; -import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.*; -import static eu.dnetlib.dhp.application.ApplicationUtils.*; +import static eu.dnetlib.dhp.utils.DHPUtils.*; import java.io.IOException; import java.util.Optional; import org.apache.commons.cli.ParseException; -import org.apache.commons.io.FileSystemUtils; import org.apache.commons.io.IOUtils; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; -import eu.dnetlib.dhp.aggregation.common.AggregationUtility; import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginReport; -import eu.dnetlib.dhp.collection.worker.utils.HttpClientParams; -import eu.dnetlib.dhp.collection.worker.utils.UnknownCollectorPluginException; import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; import eu.dnetlib.dhp.message.MessageSender; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerReporter.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerReporter.java index e0e402cfb..3a8145946 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerReporter.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerReporter.java @@ -2,10 +2,7 @@ package eu.dnetlib.dhp.collection.worker; import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.REPORT_FILE_NAME; -import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.*; -import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.MAPPER; -import static eu.dnetlib.dhp.application.ApplicationUtils.getHadoopConfiguration; -import static eu.dnetlib.dhp.application.ApplicationUtils.populateOOZIEEnv; +import static eu.dnetlib.dhp.utils.DHPUtils.*; import java.io.IOException; import java.util.Objects; @@ -13,15 +10,11 @@ import java.util.Objects; import org.apache.commons.cli.ParseException; import org.apache.commons.io.IOUtils; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.yarn.webapp.hamlet.Hamlet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; -import eu.dnetlib.dhp.aggregation.common.AggregationUtility; import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginReport; -import eu.dnetlib.dhp.collection.worker.utils.UnknownCollectorPluginException; /** * CollectorWorkerReporter diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpClientParams.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpClientParams.java similarity index 96% rename from dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpClientParams.java rename to dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpClientParams.java index e77f3680f..315dd27c2 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpClientParams.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpClientParams.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.collection.worker.utils; +package eu.dnetlib.dhp.collection.worker; /** * Bundles the http connection parameters driving the client behaviour. diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector2.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpConnector2.java similarity index 93% rename from dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector2.java rename to dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpConnector2.java index 68b1ef8ad..ee3acf432 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/HttpConnector2.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpConnector2.java @@ -1,5 +1,7 @@ -package eu.dnetlib.dhp.collection.worker.utils; +package eu.dnetlib.dhp.collection.worker; + +import static eu.dnetlib.dhp.utils.DHPUtils.*; import java.io.IOException; import java.io.InputStream; @@ -13,8 +15,6 @@ import org.apache.http.HttpHeaders; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import eu.dnetlib.dhp.collection.worker.CollectorException; - /** * Migrated from https://svn.driver.research-infrastructures.eu/driver/dnet45/modules/dnet-modular-collector-service/trunk/src/main/java/eu/dnetlib/data/collector/plugins/HttpConnector.java * @@ -162,11 +162,19 @@ public class HttpConnector2 { } } throw new CollectorException( - String.format("Unexpected status code: %s error %s", urlConn.getResponseCode(), report)); + String + .format( + "Unexpected status code: %s errors: %s", urlConn.getResponseCode(), + MAPPER.writeValueAsString(report))); } catch (MalformedURLException | SocketException | UnknownHostException e) { log.error(e.getMessage(), e); report.put(e.getClass().getName(), e.getMessage()); throw new CollectorException(e.getMessage(), e); + } catch (SocketTimeoutException e) { + log.error(e.getMessage(), e); + report.put(e.getClass().getName(), e.getMessage()); + backoffAndSleep(getClientParams().getRetryDelay() * retryNumber * 1000); + return attemptDownload(requestUrl, retryNumber + 1, report); } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/UnknownCollectorPluginException.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/UnknownCollectorPluginException.java similarity index 93% rename from dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/UnknownCollectorPluginException.java rename to dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/UnknownCollectorPluginException.java index c55d485e2..7134dd069 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/UnknownCollectorPluginException.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/UnknownCollectorPluginException.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.collection.worker.utils; +package eu.dnetlib.dhp.collection.worker; public class UnknownCollectorPluginException extends Exception { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/XmlCleaner.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/XmlCleaner.java similarity index 99% rename from dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/XmlCleaner.java rename to dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/XmlCleaner.java index 44aeb4d02..41ba02196 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/utils/XmlCleaner.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/XmlCleaner.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.collection.worker.utils; +package eu.dnetlib.dhp.collection.worker; import java.util.HashMap; import java.util.HashSet; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java index b735ecb1f..c0a03e081 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java @@ -2,8 +2,8 @@ package eu.dnetlib.dhp.transformation; import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.*; -import static eu.dnetlib.dhp.aggregation.common.AggregationUtility.*; import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; +import static eu.dnetlib.dhp.utils.DHPUtils.*; import java.io.IOException; import java.util.Map; diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java index e6fda9be0..7f597f950 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java @@ -14,7 +14,7 @@ import org.junit.jupiter.api.Test; import eu.dnetlib.dhp.actionmanager.project.utils.EXCELParser; import eu.dnetlib.dhp.collection.worker.CollectorException; -import eu.dnetlib.dhp.collection.worker.utils.HttpConnector2; +import eu.dnetlib.dhp.collection.worker.HttpConnector2; @Disabled public class EXCELParserTest { diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/CollectorWorkerApplicationTests.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/CollectorWorkerApplicationTests.java index 65c2833eb..80bafd6d8 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/CollectorWorkerApplicationTests.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/CollectorWorkerApplicationTests.java @@ -3,17 +3,13 @@ package eu.dnetlib.dhp.collector.worker; import static org.junit.jupiter.api.Assertions.assertNotNull; -import java.nio.file.Path; - import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; import com.fasterxml.jackson.databind.ObjectMapper; -import eu.dnetlib.dhp.collection.worker.CollectorWorker; -import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginFactory; -import eu.dnetlib.dhp.collection.worker.utils.HttpClientParams; +import eu.dnetlib.dhp.collection.worker.CollectorPluginFactory; +import eu.dnetlib.dhp.collection.worker.HttpClientParams; import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; @Disabled diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/utils/CollectorPluginReportTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/utils/CollectorPluginReportTest.java index 69376d5eb..d665e5b5f 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/utils/CollectorPluginReportTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/utils/CollectorPluginReportTest.java @@ -1,13 +1,14 @@ package eu.dnetlib.dhp.collector.worker.utils; +import static eu.dnetlib.dhp.utils.DHPUtils.*; + import java.io.IOException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import eu.dnetlib.dhp.aggregation.common.AggregationUtility; -import eu.dnetlib.dhp.collection.worker.utils.CollectorPluginReport; +import eu.dnetlib.dhp.collection.worker.CollectorPluginReport; public class CollectorPluginReportTest { @@ -17,11 +18,11 @@ public class CollectorPluginReportTest { r1.put("a", "b"); r1.setSuccess(true); - String s = AggregationUtility.MAPPER.writeValueAsString(r1); + String s = MAPPER.writeValueAsString(r1); Assertions.assertNotNull(s); - CollectorPluginReport r2 = AggregationUtility.MAPPER.readValue(s, CollectorPluginReport.class); + CollectorPluginReport r2 = MAPPER.readValue(s, CollectorPluginReport.class); Assertions.assertTrue(r2.isSuccess(), "should be true"); } From 50add4c61b991926fdf379502db3b3a0846769a9 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 8 Feb 2021 12:19:38 +0100 Subject: [PATCH 103/445] added requestDelay to HttpConnector2 configuration; Aggregation workflow constants moved in dhp-common --- .../model => collection}/ApiDescriptor.java | 2 +- .../java/eu/dnetlib/dhp/common/Constants.java | 20 +++++++++++ .../common/AggregationConstants.java | 15 -------- .../mdstore/MDStoreActionNode.java | 3 +- .../GenerateNativeStoreSparkJob.java | 2 +- .../collection/plugin/CollectorPlugin.java | 2 +- .../plugin/oai/OaiCollectorPlugin.java | 2 +- .../collection/worker/CollectorWorker.java | 4 +-- .../worker/CollectorWorkerApplication.java | 22 ++++++++---- .../worker/CollectorWorkerReporter.java | 2 +- .../collection/worker/HttpClientParams.java | 36 +++++++++++++++++-- .../dhp/collection/worker/HttpConnector2.java | 20 +++++++---- .../transformation/TransformSparkJobNode.java | 2 +- .../collector_worker_input_parameter.json | 14 +++++--- .../dhp/collection/oozie_app/workflow.xml | 1 + .../dhp/aggregation/AggregationJobTest.java | 2 +- .../CollectorWorkerApplicationTests.java | 2 +- .../transformation/TransformationJobTest.java | 4 +-- 18 files changed, 106 insertions(+), 49 deletions(-) rename dhp-common/src/main/java/eu/dnetlib/dhp/{collector/worker/model => collection}/ApiDescriptor.java (93%) delete mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationConstants.java diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/collector/worker/model/ApiDescriptor.java b/dhp-common/src/main/java/eu/dnetlib/dhp/collection/ApiDescriptor.java similarity index 93% rename from dhp-common/src/main/java/eu/dnetlib/dhp/collector/worker/model/ApiDescriptor.java rename to dhp-common/src/main/java/eu/dnetlib/dhp/collection/ApiDescriptor.java index 8ba30faeb..12937a197 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/collector/worker/model/ApiDescriptor.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/collection/ApiDescriptor.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.collector.worker.model; +package eu.dnetlib.dhp.collection; import java.util.HashMap; import java.util.Map; diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/common/Constants.java b/dhp-common/src/main/java/eu/dnetlib/dhp/common/Constants.java index 2b8ef4e30..eb4cb91ed 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/common/Constants.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/common/Constants.java @@ -27,4 +27,24 @@ public class Constants { coarCodeLabelMap.put("c_f1cf", "EMBARGO"); } + public static final String SEQUENCE_FILE_NAME = "/sequence_file"; + public static final String REPORT_FILE_NAME = "/report"; + public static final String MDSTORE_DATA_PATH = "/store"; + public static final String MDSTORE_SIZE_PATH = "/size"; + + public static final String COLLECTION_MODE = "collectionMode"; + public static final String METADATA_ENCODING = "metadataEncoding"; + public static final String OOZIE_WF_PATH = "oozieWfPath"; + public static final String DNET_MESSAGE_MGR_URL = "dnetMessageManagerURL"; + + public static final String MAX_NUMBER_OF_RETRY = "maxNumberOfRetry"; + public static final String REQUEST_DELAY = "requestDelay"; + public static final String RETRY_DELAY = "retryDelay"; + public static final String CONNECT_TIMEOUT = "connectTimeOut"; + public static final String READ_TIMEOUT = "readTimeOut"; + + public static final String CONTENT_TOTALITEMS = "TotalItems"; + public static final String CONTENT_INVALIDRECORDS = "InvalidRecords"; + public static final String CONTENT_TRANSFORMEDRECORDS = "transformedItems"; + } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationConstants.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationConstants.java deleted file mode 100644 index 8e0b7260d..000000000 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregationConstants.java +++ /dev/null @@ -1,15 +0,0 @@ - -package eu.dnetlib.dhp.aggregation.common; - -public class AggregationConstants { - - public static final String SEQUENCE_FILE_NAME = "/sequence_file"; - public static final String REPORT_FILE_NAME = "/report"; - public static final String MDSTORE_DATA_PATH = "/store"; - public static final String MDSTORE_SIZE_PATH = "/size"; - - public static final String CONTENT_TOTALITEMS = "TotalItems"; - public static final String CONTENT_INVALIDRECORDS = "InvalidRecords"; - public static final String CONTENT_TRANSFORMEDRECORDS = "transformedItems"; - -} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java index 9a47a1d66..829921dd8 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java @@ -1,8 +1,7 @@ package eu.dnetlib.dhp.aggregation.mdstore; -import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.*; -import static eu.dnetlib.dhp.application.ApplicationUtils.*; +import static eu.dnetlib.dhp.common.Constants.*; import static eu.dnetlib.dhp.utils.DHPUtils.*; import java.net.URI; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java index 5c24bb7ec..ee82cc94f 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java @@ -1,7 +1,7 @@ package eu.dnetlib.dhp.collection; -import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.*; +import static eu.dnetlib.dhp.common.Constants.*; import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; import static eu.dnetlib.dhp.utils.DHPUtils.*; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java index 614aa4e69..e2be481ed 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java @@ -3,9 +3,9 @@ package eu.dnetlib.dhp.collection.plugin; import java.util.stream.Stream; +import eu.dnetlib.dhp.collection.ApiDescriptor; import eu.dnetlib.dhp.collection.worker.CollectorException; import eu.dnetlib.dhp.collection.worker.CollectorPluginReport; -import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; public interface CollectorPlugin { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java index 7ec2f09be..84228abf4 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java @@ -13,11 +13,11 @@ import com.google.common.base.Splitter; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; +import eu.dnetlib.dhp.collection.ApiDescriptor; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; import eu.dnetlib.dhp.collection.worker.CollectorException; import eu.dnetlib.dhp.collection.worker.CollectorPluginReport; import eu.dnetlib.dhp.collection.worker.HttpClientParams; -import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; public class OaiCollectorPlugin implements CollectorPlugin { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java index 71dee0d03..c2d32019d 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java @@ -1,7 +1,7 @@ package eu.dnetlib.dhp.collection.worker; -import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.SEQUENCE_FILE_NAME; +import static eu.dnetlib.dhp.common.Constants.SEQUENCE_FILE_NAME; import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; @@ -15,8 +15,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; +import eu.dnetlib.dhp.collection.ApiDescriptor; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; -import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; import eu.dnetlib.dhp.message.MessageSender; public class CollectorWorker { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java index a6c254d42..6e4237bee 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java @@ -1,7 +1,7 @@ package eu.dnetlib.dhp.collection.worker; -import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.*; +import static eu.dnetlib.dhp.common.Constants.*; import static eu.dnetlib.dhp.utils.DHPUtils.*; import java.io.IOException; @@ -17,7 +17,7 @@ import org.slf4j.LoggerFactory; import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; +import eu.dnetlib.dhp.collection.ApiDescriptor; import eu.dnetlib.dhp.message.MessageSender; /** @@ -55,7 +55,7 @@ public class CollectorWorkerApplication { final String mdStoreVersion = argumentParser.get("mdStoreVersion"); log.info("mdStoreVersion is {}", mdStoreVersion); - final String dnetMessageManagerURL = argumentParser.get("dnetMessageManagerURL"); + final String dnetMessageManagerURL = argumentParser.get(DNET_MESSAGE_MGR_URL); log.info("dnetMessageManagerURL is {}", dnetMessageManagerURL); final String workflowId = argumentParser.get("workflowId"); @@ -87,15 +87,23 @@ public class CollectorWorkerApplication { clientParams .setMaxNumberOfRetry( Optional - .ofNullable(argumentParser.get("maxNumberOfRetry")) + .ofNullable(argumentParser.get(MAX_NUMBER_OF_RETRY)) .map(Integer::parseInt) .orElse(HttpClientParams._maxNumberOfRetry)); log.info("maxNumberOfRetry is {}", clientParams.getMaxNumberOfRetry()); + clientParams + .setRequestDelay( + Optional + .ofNullable(argumentParser.get(REQUEST_DELAY)) + .map(Integer::parseInt) + .orElse(HttpClientParams._requestDelay)); + log.info("requestDelay is {}", clientParams.getRequestDelay()); + clientParams .setRetryDelay( Optional - .ofNullable(argumentParser.get("retryDelay")) + .ofNullable(argumentParser.get(RETRY_DELAY)) .map(Integer::parseInt) .orElse(HttpClientParams._retryDelay)); log.info("retryDelay is {}", clientParams.getRetryDelay()); @@ -103,7 +111,7 @@ public class CollectorWorkerApplication { clientParams .setConnectTimeOut( Optional - .ofNullable(argumentParser.get("connectTimeOut")) + .ofNullable(argumentParser.get(CONNECT_TIMEOUT)) .map(Integer::parseInt) .orElse(HttpClientParams._connectTimeOut)); log.info("connectTimeOut is {}", clientParams.getConnectTimeOut()); @@ -111,7 +119,7 @@ public class CollectorWorkerApplication { clientParams .setReadTimeOut( Optional - .ofNullable(argumentParser.get("readTimeOut")) + .ofNullable(argumentParser.get(READ_TIMEOUT)) .map(Integer::parseInt) .orElse(HttpClientParams._readTimeOut)); log.info("readTimeOut is {}", clientParams.getReadTimeOut()); diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerReporter.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerReporter.java index 3a8145946..3f6fc4784 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerReporter.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerReporter.java @@ -1,7 +1,7 @@ package eu.dnetlib.dhp.collection.worker; -import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.REPORT_FILE_NAME; +import static eu.dnetlib.dhp.common.Constants.REPORT_FILE_NAME; import static eu.dnetlib.dhp.utils.DHPUtils.*; import java.io.IOException; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpClientParams.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpClientParams.java index 315dd27c2..f45790460 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpClientParams.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpClientParams.java @@ -6,22 +6,46 @@ package eu.dnetlib.dhp.collection.worker; */ public class HttpClientParams { + // Defaults public static int _maxNumberOfRetry = 3; + public static int _requestDelay = 0; // milliseconds public static int _retryDelay = 10; // seconds public static int _connectTimeOut = 10; // seconds public static int _readTimeOut = 30; // seconds + /** + * Maximum number of allowed retires before failing + */ private int maxNumberOfRetry; + + /** + * Delay between request (Milliseconds) + */ + private int requestDelay; + + /** + * Time to wait after a failure before retrying (Seconds) + */ private int retryDelay; + + /** + * Connect timeout (Seconds) + */ private int connectTimeOut; + + /** + * Read timeout (Seconds) + */ private int readTimeOut; public HttpClientParams() { - this(_maxNumberOfRetry, _retryDelay, _connectTimeOut, _readTimeOut); + this(_maxNumberOfRetry, _requestDelay, _retryDelay, _connectTimeOut, _readTimeOut); } - public HttpClientParams(int maxNumberOfRetry, int retryDelay, int connectTimeOut, int readTimeOut) { + public HttpClientParams(int maxNumberOfRetry, int requestDelay, int retryDelay, int connectTimeOut, + int readTimeOut) { this.maxNumberOfRetry = maxNumberOfRetry; + this.requestDelay = requestDelay; this.retryDelay = retryDelay; this.connectTimeOut = connectTimeOut; this.readTimeOut = readTimeOut; @@ -35,6 +59,14 @@ public class HttpClientParams { this.maxNumberOfRetry = maxNumberOfRetry; } + public int getRequestDelay() { + return requestDelay; + } + + public void setRequestDelay(int requestDelay) { + this.requestDelay = requestDelay; + } + public int getRetryDelay() { return retryDelay; } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpConnector2.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpConnector2.java index ee3acf432..368c89509 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpConnector2.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpConnector2.java @@ -18,7 +18,7 @@ import org.slf4j.LoggerFactory; /** * Migrated from https://svn.driver.research-infrastructures.eu/driver/dnet45/modules/dnet-modular-collector-service/trunk/src/main/java/eu/dnetlib/data/collector/plugins/HttpConnector.java * - * @author jochen, michele, andrea, alessia + * @author jochen, michele, andrea, alessia, claudio */ public class HttpConnector2 { @@ -83,14 +83,22 @@ public class HttpConnector2 { final CollectorPluginReport report) throws CollectorException, IOException { if (retryNumber > getClientParams().getMaxNumberOfRetry()) { - throw new CollectorException("Max number of retries exceeded. Cause: \n " + report); + final String msg = String + .format( + "Max number of retries (%s/%s) exceeded, failing.", + retryNumber, getClientParams().getMaxNumberOfRetry()); + log.error(msg); + throw new CollectorException(msg); } - log.info("Downloading attempt {} [{}]", retryNumber, requestUrl); + log.info("Request attempt {} [{}]", retryNumber, requestUrl); InputStream input = null; try { + if (getClientParams().getRequestDelay() > 0) { + backoffAndSleep(getClientParams().getRequestDelay()); + } final HttpURLConnection urlConn = (HttpURLConnection) new URL(requestUrl).openConnection(); urlConn.setInstanceFollowRedirects(false); urlConn.setReadTimeout(getClientParams().getReadTimeOut() * 1000); @@ -190,10 +198,10 @@ public class HttpConnector2 { } } - private void backoffAndSleep(int sleepTime) throws CollectorException { - log.info("I'm going to sleep for {}ms", sleepTime); + private void backoffAndSleep(int sleepTimeMs) throws CollectorException { + log.info("I'm going to sleep for {}ms", sleepTimeMs); try { - Thread.sleep(sleepTime); + Thread.sleep(sleepTimeMs); } catch (InterruptedException e) { log.error(e.getMessage(), e); throw new CollectorException(e); diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java index c0a03e081..e628e7645 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java @@ -1,7 +1,7 @@ package eu.dnetlib.dhp.transformation; -import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.*; +import static eu.dnetlib.dhp.common.Constants.*; import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; import static eu.dnetlib.dhp.utils.DHPUtils.*; diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_worker_input_parameter.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_worker_input_parameter.json index f3eaf2d71..cd4b8224b 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_worker_input_parameter.json +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_worker_input_parameter.json @@ -30,25 +30,31 @@ "paramRequired": true }, { - "paramName": "mr", + "paramName": "mnr", "paramLongName": "maxNumberOfRetry", "paramDescription": "the maximum number of admitted connection retries", "paramRequired": false }, { - "paramName": "rd", + "paramName": "rqd", + "paramLongName": "requestDelay", + "paramDescription": "the delay (ms) between requests", + "paramRequired": false + }, + { + "paramName": "rtd", "paramLongName": "retryDelay", "paramDescription": "the delay (ms) between retries", "paramRequired": false }, { - "paramName": "ct", + "paramName": "cto", "paramLongName": "connectTimeOut", "paramDescription": "the maximum allowed time (ms) to connect to the remote host", "paramRequired": false }, { - "paramName": "rt", + "paramName": "rto", "paramLongName": "readTimeOut", "paramDescription": "the maximum allowed time (ms) to receive content from the remote host", "paramRequired": false diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml index e7f6b9201..fe8eea370 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml @@ -95,6 +95,7 @@ --dnetMessageManagerURL${dnetMessageManagerURL} --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} --maxNumberOfRetry${maxNumberOfRetry} + --requestDelay${requestDelay} --retryDelay${retryDelay} --connectTimeOut${connectTimeOut} --readTimeOut${readTimeOut} diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java index 3cb66d5ee..ff3ff3b6e 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java @@ -1,7 +1,7 @@ package eu.dnetlib.dhp.aggregation; -import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.MDSTORE_DATA_PATH; +import static eu.dnetlib.dhp.common.Constants.MDSTORE_DATA_PATH; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/CollectorWorkerApplicationTests.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/CollectorWorkerApplicationTests.java index 80bafd6d8..975ef944e 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/CollectorWorkerApplicationTests.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/CollectorWorkerApplicationTests.java @@ -8,9 +8,9 @@ import org.junit.jupiter.api.Test; import com.fasterxml.jackson.databind.ObjectMapper; +import eu.dnetlib.dhp.collection.ApiDescriptor; import eu.dnetlib.dhp.collection.worker.CollectorPluginFactory; import eu.dnetlib.dhp.collection.worker.HttpClientParams; -import eu.dnetlib.dhp.collector.worker.model.ApiDescriptor; @Disabled public class CollectorWorkerApplicationTests { diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java index 9d6dacf0c..997727e33 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java @@ -1,14 +1,12 @@ package eu.dnetlib.dhp.transformation; -import static eu.dnetlib.dhp.aggregation.common.AggregationConstants.MDSTORE_DATA_PATH; +import static eu.dnetlib.dhp.common.Constants.MDSTORE_DATA_PATH; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.lenient; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; From bebc54d5bff5fe4f03e0b596be6377835cc72cab Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 8 Feb 2021 18:06:25 +0100 Subject: [PATCH 104/445] seq file storing native records is now compressed --- .../eu/dnetlib/dhp/collection/worker/CollectorWorker.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java index c2d32019d..945eff8b0 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java @@ -11,6 +11,7 @@ import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.SequenceFile; import org.apache.hadoop.io.Text; +import org.apache.hadoop.io.compress.GzipCodec; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,7 +64,8 @@ public class CollectorWorker { conf, SequenceFile.Writer.file(new Path(outputPath)), SequenceFile.Writer.keyClass(IntWritable.class), - SequenceFile.Writer.valueClass(Text.class))) { + SequenceFile.Writer.valueClass(Text.class), + SequenceFile.Writer.compression(SequenceFile.CompressionType.BLOCK, new GzipCodec()))) { final IntWritable key = new IntWritable(counter.get()); final Text value = new Text(); plugin From bae029f8288e20c5888e1ef39489d88f2a08ce96 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 8 Feb 2021 18:07:23 +0100 Subject: [PATCH 105/445] collection_java_xmx allows to declare the heap size allocated for the java actions involved in the metadata collectionw workflow --- .../aggregation/mdstore/MDStoreActionNode.java | 2 ++ .../worker/CollectorWorkerApplication.java | 4 +++- .../dhp/collection/oozie_app/workflow.xml | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java index 829921dd8..09f3ffd63 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/mdstore/MDStoreActionNode.java @@ -45,6 +45,8 @@ public class MDStoreActionNode { "/eu/dnetlib/dhp/collection/mdstore_action_parameters.json"))); argumentParser.parseArgument(args); + log.info("Java Xmx: {}m", Runtime.getRuntime().maxMemory() / (1024 * 1024)); + final MDAction action = MDAction.valueOf(argumentParser.get("action")); log.info("Current action is {}", action); diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java index 6e4237bee..17f09ee5a 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java @@ -41,11 +41,13 @@ public class CollectorWorkerApplication { final ArgumentApplicationParser argumentParser = new ArgumentApplicationParser( IOUtils .toString( - CollectorWorker.class + CollectorWorkerApplication.class .getResourceAsStream( "/eu/dnetlib/dhp/collection/collector_worker_input_parameter.json"))); argumentParser.parseArgument(args); + log.info("Java Xmx: {}m", Runtime.getRuntime().maxMemory() / (1024 * 1024)); + final String hdfsuri = argumentParser.get("namenode"); log.info("hdfsURI is {}", hdfsuri); diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml index fe8eea370..5497b2c50 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml @@ -41,6 +41,14 @@ collectionMode Should be REFRESH or INCREMENTAL + + + collection_java_xmx + -Xmx200m + Used to configure the heap size for the map JVM process. Should be 80% of mapreduce.map.memory.mb. + + + @@ -65,6 +73,7 @@ eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode + ${collection_java_xmx} --actionREAD_LOCK --mdStoreID${mdStoreID} --mdStoreManagerURI${mdStoreManagerURI} @@ -77,6 +86,7 @@ eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode + ${collection_java_xmx} --actionNEW_VERSION --mdStoreID${mdStoreID} --mdStoreManagerURI${mdStoreManagerURI} @@ -89,6 +99,7 @@ eu.dnetlib.dhp.collection.worker.CollectorWorkerApplication + ${collection_java_xmx} --apidescriptor${apiDescription} --namenode${nameNode} --workflowId${workflowId} @@ -108,6 +119,7 @@ eu.dnetlib.dhp.collection.worker.CollectorWorkerReporter + ${collection_java_xmx} --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} --namenode${nameNode} @@ -153,6 +165,7 @@ eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode + ${collection_java_xmx} --actionREAD_UNLOCK --mdStoreManagerURI${mdStoreManagerURI} --readMDStoreId${wf:actionData('BeginRead')['mdStoreReadLockVersion']} @@ -164,6 +177,7 @@ eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode + ${collection_java_xmx} --actionCOMMIT --namenode${nameNode} --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} @@ -184,6 +198,7 @@ eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode + ${collection_java_xmx} --actionREAD_UNLOCK --mdStoreManagerURI${mdStoreManagerURI} --readMDStoreId${wf:actionData('BeginRead')['mdStoreReadLockVersion']} @@ -195,6 +210,7 @@ eu.dnetlib.dhp.aggregation.mdstore.MDStoreActionNode + ${collection_java_xmx} --actionROLLBACK --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} --mdStoreManagerURI${mdStoreManagerURI} From 4b2124a18e611fad6db3de5f3f5947d406f2b88b Mon Sep 17 00:00:00 2001 From: miconis Date: Wed, 10 Feb 2021 11:51:50 +0100 Subject: [PATCH 106/445] implementation of the openorgs wfs, implementation of the raw_all wf to migrate openorgs db entities --- .../dhp/oa/dedup/SparkCopyOpenorgs.java | 140 +++---- .../oa/dedup/SparkCopyOpenorgsMergeRels.java | 181 +++++++++ ...els.java => SparkCopyOpenorgsSimRels.java} | 97 ++--- .../dedup/SparkCopyRelationsNoOpenorgs.java | 110 ++++++ .../dhp/oa/dedup/SparkRemoveDiffRels.java | 363 +++++++++--------- ... => copyOpenorgsMergeRels_parameters.json} | 6 +- .../oa/dedup/openorgs/oozie_app/workflow.xml | 58 ++- .../dhp/oa/dedup/scan/oozie_app/workflow.xml | 49 ++- .../raw/MigrateDbEntitiesApplication.java | 103 +++-- .../oa/graph/raw/common/MigrateAction.java | 9 + .../oa/graph/raw_all/oozie_app/workflow.xml | 38 +- .../sql/queryOrganizationsFromOpenOrgsDB.sql | 37 +- .../sql/querySimilarityFromOpenOrgsDB.sql | 52 ++- 13 files changed, 858 insertions(+), 385 deletions(-) create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java rename dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/{SparkCopyRels.java => SparkCopyOpenorgsSimRels.java} (61%) create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRelationsNoOpenorgs.java rename dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/{copyRels_parameters.json => copyOpenorgsMergeRels_parameters.json} (84%) create mode 100644 dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/MigrateAction.java diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java index 12ae4e73a..aa7a131e7 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java @@ -1,12 +1,9 @@ + package eu.dnetlib.dhp.oa.dedup; -import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.schema.common.EntityType; -import eu.dnetlib.dhp.schema.common.ModelSupport; -import eu.dnetlib.dhp.schema.oaf.OafEntity; -import eu.dnetlib.dhp.utils.ISLookupClientFactory; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import java.io.IOException; +import java.util.Optional; + import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; import org.apache.spark.api.java.function.MapFunction; @@ -18,83 +15,88 @@ import org.dom4j.DocumentException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.util.Optional; +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.common.EntityType; +import eu.dnetlib.dhp.schema.common.ModelSupport; +import eu.dnetlib.dhp.schema.oaf.OafEntity; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -public class SparkCopyOpenorgs extends AbstractSparkAction{ - private static final Logger log = LoggerFactory.getLogger(SparkCopyRels.class); +public class SparkCopyOpenorgs extends AbstractSparkAction { + private static final Logger log = LoggerFactory.getLogger(SparkCopyOpenorgs.class); - public SparkCopyOpenorgs(ArgumentApplicationParser parser, SparkSession spark) { - super(parser, spark); - } + public SparkCopyOpenorgs(ArgumentApplicationParser parser, SparkSession spark) { + super(parser, spark); + } - public static void main(String[] args) throws Exception { - ArgumentApplicationParser parser = new ArgumentApplicationParser( - IOUtils - .toString( - SparkCreateSimRels.class - .getResourceAsStream( - "/eu/dnetlib/dhp/oa/dedup/copyOpenorgs_parameters.json"))); - parser.parseArgument(args); + public static void main(String[] args) throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCreateSimRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/copyOpenorgs_parameters.json"))); + parser.parseArgument(args); - SparkConf conf = new SparkConf(); - new SparkCopyOpenorgs(parser, getSparkSession(conf)) - .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); - } + SparkConf conf = new SparkConf(); + new SparkCopyOpenorgs(parser, getSparkSession(conf)) + .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); + } - @Override - public void run(ISLookUpService isLookUpService) - throws DocumentException, IOException, ISLookUpException { + @Override + public void run(ISLookUpService isLookUpService) + throws DocumentException, IOException, ISLookUpException { - // read oozie parameters - final String graphBasePath = parser.get("graphBasePath"); - final String actionSetId = parser.get("actionSetId"); - final String workingPath = parser.get("workingPath"); - final int numPartitions = Optional - .ofNullable(parser.get("numPartitions")) - .map(Integer::valueOf) - .orElse(NUM_PARTITIONS); + // read oozie parameters + final String graphBasePath = parser.get("graphBasePath"); + final String actionSetId = parser.get("actionSetId"); + final String workingPath = parser.get("workingPath"); + final int numPartitions = Optional + .ofNullable(parser.get("numPartitions")) + .map(Integer::valueOf) + .orElse(NUM_PARTITIONS); - log.info("numPartitions: '{}'", numPartitions); - log.info("graphBasePath: '{}'", graphBasePath); - log.info("actionSetId: '{}'", actionSetId); - log.info("workingPath: '{}'", workingPath); + log.info("numPartitions: '{}'", numPartitions); + log.info("graphBasePath: '{}'", graphBasePath); + log.info("actionSetId: '{}'", actionSetId); + log.info("workingPath: '{}'", workingPath); - String subEntity = "organization"; - log.info("Copying openorgs to the working dir"); + String subEntity = "organization"; + log.info("Copying openorgs to the working dir"); - final String outputPath = DedupUtility.createDedupRecordPath(workingPath, actionSetId, subEntity); - removeOutputDir(spark, outputPath); + final String outputPath = DedupUtility.createDedupRecordPath(workingPath, actionSetId, subEntity); + removeOutputDir(spark, outputPath); - final String entityPath = DedupUtility.createEntityPath(graphBasePath, subEntity); + final String entityPath = DedupUtility.createEntityPath(graphBasePath, subEntity); - final Class clazz = ModelSupport.entityTypes.get(EntityType.valueOf(subEntity)); + final Class clazz = ModelSupport.entityTypes.get(EntityType.valueOf(subEntity)); - filterEntities(spark, entityPath, clazz) - .write() - .mode(SaveMode.Overwrite) - .option("compression", "gzip") - .json(outputPath); + filterEntities(spark, entityPath, clazz) + .write() + .mode(SaveMode.Overwrite) + .option("compression", "gzip") + .json(outputPath); - } + } - public static Dataset filterEntities( - final SparkSession spark, - final String entitiesInputPath, - final Class clazz) { + public static Dataset filterEntities( + final SparkSession spark, + final String entitiesInputPath, + final Class clazz) { - // - Dataset entities = spark - .read() - .textFile(entitiesInputPath) - .map( - (MapFunction) it -> { - T entity = OBJECT_MAPPER.readValue(it, clazz); - return entity; - }, - Encoders.kryo(clazz)); + // + Dataset entities = spark + .read() + .textFile(entitiesInputPath) + .map( + (MapFunction) it -> { + T entity = OBJECT_MAPPER.readValue(it, clazz); + return entity; + }, + Encoders.kryo(clazz)); - return entities.filter(entities.col("id").contains("openorgs____")); - } + return entities.filter(entities.col("id").contains("openorgs____")); + } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java new file mode 100644 index 000000000..d705fca6b --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java @@ -0,0 +1,181 @@ + +package eu.dnetlib.dhp.oa.dedup; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import eu.dnetlib.dhp.schema.oaf.KeyValue; +import eu.dnetlib.dhp.schema.oaf.Qualifier; +import eu.dnetlib.pace.config.DedupConfig; +import org.apache.commons.io.IOUtils; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaRDD; +import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.SparkSession; +import org.dom4j.DocumentException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.oaf.DataInfo; +import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; + +//copy simrels (verified) from relation to the workdir in order to make them available for the deduplication +public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { + private static final Logger log = LoggerFactory.getLogger(SparkCopyOpenorgsMergeRels.class); + public static final String PROVENANCE_ACTION_CLASS = "sysimport:dedup"; + public static final String DNET_PROVENANCE_ACTIONS = "dnet:provenanceActions"; + + public SparkCopyOpenorgsMergeRels(ArgumentApplicationParser parser, SparkSession spark) { + super(parser, spark); + } + + public static void main(String[] args) throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCopyOpenorgsMergeRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/copyOpenorgsMergeRels_parameters.json"))); + parser.parseArgument(args); + + SparkConf conf = new SparkConf(); + new SparkCopyOpenorgsMergeRels(parser, getSparkSession(conf)) + .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); + } + + @Override + public void run(ISLookUpService isLookUpService) + throws DocumentException, IOException, ISLookUpException { + + // read oozie parameters + final String graphBasePath = parser.get("graphBasePath"); + final String actionSetId = parser.get("actionSetId"); + final String workingPath = parser.get("workingPath"); + final int numPartitions = Optional + .ofNullable(parser.get("numPartitions")) + .map(Integer::valueOf) + .orElse(NUM_PARTITIONS); + + log.info("numPartitions: '{}'", numPartitions); + log.info("graphBasePath: '{}'", graphBasePath); + log.info("actionSetId: '{}'", actionSetId); + log.info("workingPath: '{}'", workingPath); + + log.info("Copying OpenOrgs Merge Rels"); + + final String outputPath = DedupUtility.createMergeRelPath(workingPath, actionSetId, "organization"); + + removeOutputDir(spark, outputPath); + + final String relationPath = DedupUtility.createEntityPath(graphBasePath, "relation"); + + DedupConfig dedupConf = getConfigurations(isLookUpService, actionSetId).get(0); + + JavaRDD rawRels = spark + .read() + .textFile(relationPath) + .map(patchRelFn(), Encoders.bean(Relation.class)) + .toJavaRDD() + .filter(this::isOpenorgs) //takes only relations coming from openorgs + .filter(this::filterOpenorgsRels) //takes only isSimilarTo relations between organizations from openorgs + .filter(this::excludeOpenorgsMesh) //excludes relations between an organization and an openorgsmesh + .filter(this::excludeNonOpenorgs); //excludes relations with no openorgs id involved + + //turn openorgs isSimilarTo relations into mergerels + JavaRDD mergeRels = rawRels.flatMap(rel -> { + List mergerels = new ArrayList<>(); + + String openorgsId = rel.getSource().contains("openorgs____")? rel.getSource() : rel.getTarget(); + String mergedId = rel.getSource().contains("openorgs____")? rel.getTarget() : rel.getSource(); + + mergerels.add(rel(openorgsId, mergedId, "merges", dedupConf)); + mergerels.add(rel(mergedId, openorgsId, "isMergedIn", dedupConf)); + + return mergerels.iterator(); + }); + + mergeRels.saveAsTextFile(outputPath); + } + + private static MapFunction patchRelFn() { + return value -> { + final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); + if (rel.getDataInfo() == null) { + rel.setDataInfo(new DataInfo()); + } + return rel; + }; + } + + private boolean filterOpenorgsRels(Relation rel) { + + if (rel.getRelClass().equals("isSimilarTo") && rel.getRelType().equals("organizationOrganization") && rel.getSubRelType().equals("dedup")) + return true; + return false; + } + + private boolean isOpenorgs(Relation rel) { + + if (rel.getCollectedfrom() != null) { + for (KeyValue k: rel.getCollectedfrom()) { + if (k.getValue().equals("OpenOrgs Database")) { + return true; + } + } + } + return false; + } + + private boolean excludeOpenorgsMesh(Relation rel) { + + if (rel.getSource().equals("openorgsmesh") || rel.getTarget().equals("openorgsmesh")) { + return false; + } + return true; + } + + private boolean excludeNonOpenorgs(Relation rel) { + + if (rel.getSource().equals("openorgs____") || rel.getTarget().equals("openorgs____")) { + return true; + } + return false; + } + + private Relation rel(String source, String target, String relClass, DedupConfig dedupConf) { + + String entityType = dedupConf.getWf().getEntityType(); + + Relation r = new Relation(); + r.setSource(source); + r.setTarget(target); + r.setRelClass(relClass); + r.setRelType(entityType + entityType.substring(0, 1).toUpperCase() + entityType.substring(1)); + r.setSubRelType("dedup"); + + DataInfo info = new DataInfo(); + info.setDeletedbyinference(false); + info.setInferred(true); + info.setInvisible(false); + info.setInferenceprovenance(dedupConf.getWf().getConfigurationId()); + Qualifier provenanceAction = new Qualifier(); + provenanceAction.setClassid(PROVENANCE_ACTION_CLASS); + provenanceAction.setClassname(PROVENANCE_ACTION_CLASS); + provenanceAction.setSchemeid(DNET_PROVENANCE_ACTIONS); + provenanceAction.setSchemename(DNET_PROVENANCE_ACTIONS); + info.setProvenanceaction(provenanceAction); + + // TODO calculate the trust value based on the similarity score of the elements in the CC + // info.setTrust(); + + r.setDataInfo(info); + return r; + } +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java similarity index 61% rename from dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRels.java rename to dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java index 802085ab9..3ce676f84 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java @@ -1,29 +1,37 @@ + package eu.dnetlib.dhp.oa.dedup; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import eu.dnetlib.dhp.schema.oaf.KeyValue; +import eu.dnetlib.dhp.schema.oaf.Qualifier; +import eu.dnetlib.pace.config.DedupConfig; +import org.apache.commons.io.IOUtils; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaRDD; +import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.SaveMode; +import org.apache.spark.sql.SparkSession; +import org.dom4j.DocumentException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.schema.oaf.DataInfo; import eu.dnetlib.dhp.schema.oaf.Relation; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import org.apache.commons.io.IOUtils; -import org.apache.spark.SparkConf; -import org.apache.spark.api.java.JavaRDD; -import org.apache.spark.api.java.function.MapFunction; -import org.apache.spark.sql.Encoders; -import org.apache.spark.sql.SparkSession; -import org.dom4j.DocumentException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.Optional; //copy simrels (verified) from relation to the workdir in order to make them available for the deduplication -public class SparkCopyRels extends AbstractSparkAction{ - private static final Logger log = LoggerFactory.getLogger(SparkCopyRels.class); +public class SparkCopyOpenorgsSimRels extends AbstractSparkAction { + private static final Logger log = LoggerFactory.getLogger(SparkCopyOpenorgsMergeRels.class); - public SparkCopyRels(ArgumentApplicationParser parser, SparkSession spark) { + public SparkCopyOpenorgsSimRels(ArgumentApplicationParser parser, SparkSession spark) { super(parser, spark); } @@ -31,13 +39,13 @@ public class SparkCopyRels extends AbstractSparkAction{ ArgumentApplicationParser parser = new ArgumentApplicationParser( IOUtils .toString( - SparkCopyRels.class + SparkCopyOpenorgsSimRels.class .getResourceAsStream( - "/eu/dnetlib/dhp/oa/dedup/copyRels_parameters.json"))); + "/eu/dnetlib/dhp/oa/dedup/copyOpenorgsMergeRels_parameters.json"))); parser.parseArgument(args); SparkConf conf = new SparkConf(); - new SparkCopyRels(parser, getSparkSession(conf)) + new SparkCopyOpenorgsSimRels(parser, getSparkSession(conf)) .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); } @@ -49,8 +57,6 @@ public class SparkCopyRels extends AbstractSparkAction{ final String graphBasePath = parser.get("graphBasePath"); final String actionSetId = parser.get("actionSetId"); final String workingPath = parser.get("workingPath"); - final String destination = parser.get("destination"); - final String entity = parser.get("entityType"); final int numPartitions = Optional .ofNullable(parser.get("numPartitions")) .map(Integer::valueOf) @@ -60,30 +66,24 @@ public class SparkCopyRels extends AbstractSparkAction{ log.info("graphBasePath: '{}'", graphBasePath); log.info("actionSetId: '{}'", actionSetId); log.info("workingPath: '{}'", workingPath); - log.info("entity: '{}'", entity); - log.info("Copying " + destination + " for: '{}'", entity); + log.info("Copying OpenOrgs SimRels"); - final String outputPath; - if (destination.contains("mergerel")) { - outputPath = DedupUtility.createMergeRelPath(workingPath, actionSetId, entity); - } - else { - outputPath = DedupUtility.createSimRelPath(workingPath, actionSetId, entity); - } + final String outputPath = DedupUtility.createSimRelPath(workingPath, actionSetId, "organization"); removeOutputDir(spark, outputPath); final String relationPath = DedupUtility.createEntityPath(graphBasePath, "relation"); - JavaRDD simRels = - spark.read() - .textFile(relationPath) - .map(patchRelFn(), Encoders.bean(Relation.class)) - .toJavaRDD() - .filter(r -> filterRels(r, entity)); + JavaRDD rawRels = spark + .read() + .textFile(relationPath) + .map(patchRelFn(), Encoders.bean(Relation.class)) + .toJavaRDD() + .filter(this::isOpenorgs) + .filter(this::filterOpenorgsRels); - simRels.saveAsTextFile(outputPath); + save(spark.createDataset(rawRels.rdd(),Encoders.bean(Relation.class)), outputPath, SaveMode.Append); } private static MapFunction patchRelFn() { @@ -96,20 +96,23 @@ public class SparkCopyRels extends AbstractSparkAction{ }; } - private boolean filterRels(Relation rel, String entityType) { + private boolean filterOpenorgsRels(Relation rel) { - switch(entityType) { - case "result": - if (rel.getRelClass().equals("isSimilarTo") && rel.getRelType().equals("resultResult") && rel.getSubRelType().equals("dedup")) + if (rel.getRelClass().equals("isSimilarTo") && rel.getRelType().equals("organizationOrganization") && rel.getSubRelType().equals("dedup")) + return true; + return false; + } + + private boolean isOpenorgs(Relation rel) { + + if (rel.getCollectedfrom() != null) { + for (KeyValue k: rel.getCollectedfrom()) { + if (k.getValue().equals("OpenOrgs Database")) { return true; - break; - case "organization": - if (rel.getRelClass().equals("isSimilarTo") && rel.getRelType().equals("organizationOrganization") && rel.getSubRelType().equals("dedup")) - return true; - break; - default: - return false; + } + } } return false; } } + diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRelationsNoOpenorgs.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRelationsNoOpenorgs.java new file mode 100644 index 000000000..319c40d8d --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRelationsNoOpenorgs.java @@ -0,0 +1,110 @@ +package eu.dnetlib.dhp.oa.dedup; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.common.ModelSupport; +import eu.dnetlib.dhp.schema.oaf.*; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import eu.dnetlib.pace.util.MapDocumentUtil; +import org.apache.commons.io.IOUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FileStatus; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.io.compress.GzipCodec; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaPairRDD; +import org.apache.spark.api.java.JavaRDD; +import org.apache.spark.api.java.JavaSparkContext; +import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.api.java.function.PairFunction; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.Row; +import org.apache.spark.sql.SparkSession; +import org.dom4j.DocumentException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import scala.Tuple2; + +import java.io.IOException; +import java.util.Optional; + +public class SparkCopyRelationsNoOpenorgs extends AbstractSparkAction { + + private static final Logger log = LoggerFactory.getLogger(SparkUpdateEntity.class); + + private static final String IDJSONPATH = "$.id"; + + public SparkCopyRelationsNoOpenorgs(ArgumentApplicationParser parser, SparkSession spark) { + super(parser, spark); + } + + public static void main(String[] args) throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCopyRelationsNoOpenorgs.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/updateEntity_parameters.json"))); + parser.parseArgument(args); + + SparkConf conf = new SparkConf(); + conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer"); + conf.registerKryoClasses(ModelSupport.getOafModelClasses()); + + new SparkUpdateEntity(parser, getSparkSession(conf)) + .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); + } + + public void run(ISLookUpService isLookUpService) throws IOException { + + final String graphBasePath = parser.get("graphBasePath"); + final String workingPath = parser.get("workingPath"); + final String dedupGraphPath = parser.get("dedupGraphPath"); + + log.info("graphBasePath: '{}'", graphBasePath); + log.info("workingPath: '{}'", workingPath); + log.info("dedupGraphPath: '{}'", dedupGraphPath); + + final JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); + + final String relationPath = DedupUtility.createEntityPath(graphBasePath, "relation"); + final String outputPath = DedupUtility.createEntityPath(dedupGraphPath, "relation"); + + removeOutputDir(spark, outputPath); + + JavaRDD simRels = spark + .read() + .textFile(relationPath) + .map(patchRelFn(), Encoders.bean(Relation.class)) + .toJavaRDD() + .filter(this::excludeOpenorgsRels); + + simRels.saveAsTextFile(outputPath); + + } + + private static MapFunction patchRelFn() { + return value -> { + final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); + if (rel.getDataInfo() == null) { + rel.setDataInfo(new DataInfo()); + } + return rel; + }; + } + + private boolean excludeOpenorgsRels(Relation rel) { + + if (rel.getCollectedfrom() != null) { + for (KeyValue k: rel.getCollectedfrom()) { + if (k.getValue().equals("OpenOrgs Database")) { + return false; + } + } + } + return true; + } +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkRemoveDiffRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkRemoveDiffRels.java index 030f3b783..6f012e00a 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkRemoveDiffRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkRemoveDiffRels.java @@ -1,18 +1,15 @@ + package eu.dnetlib.dhp.oa.dedup; -import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.oa.dedup.graph.ConnectedComponent; -import eu.dnetlib.dhp.oa.dedup.graph.GraphProcessor; -import eu.dnetlib.dhp.oa.dedup.model.Block; -import eu.dnetlib.dhp.schema.oaf.DataInfo; -import eu.dnetlib.dhp.schema.oaf.Qualifier; -import eu.dnetlib.dhp.schema.oaf.Relation; -import eu.dnetlib.dhp.utils.ISLookupClientFactory; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import eu.dnetlib.pace.config.DedupConfig; -import eu.dnetlib.pace.model.MapDocument; -import eu.dnetlib.pace.util.MapDocumentUtil; +import static eu.dnetlib.dhp.oa.dedup.SparkCreateMergeRels.DNET_PROVENANCE_ACTIONS; +import static eu.dnetlib.dhp.oa.dedup.SparkCreateMergeRels.PROVENANCE_ACTION_CLASS; +import static eu.dnetlib.dhp.oa.dedup.SparkCreateMergeRels.hash; + +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaPairRDD; @@ -29,205 +26,215 @@ import org.apache.spark.sql.SparkSession; import org.dom4j.DocumentException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.oa.dedup.graph.ConnectedComponent; +import eu.dnetlib.dhp.oa.dedup.graph.GraphProcessor; +import eu.dnetlib.dhp.oa.dedup.model.Block; +import eu.dnetlib.dhp.schema.oaf.DataInfo; +import eu.dnetlib.dhp.schema.oaf.Qualifier; +import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import eu.dnetlib.pace.config.DedupConfig; +import eu.dnetlib.pace.model.MapDocument; +import eu.dnetlib.pace.util.MapDocumentUtil; import scala.Tuple2; -import java.io.IOException; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -import static eu.dnetlib.dhp.oa.dedup.SparkCreateMergeRels.DNET_PROVENANCE_ACTIONS; -import static eu.dnetlib.dhp.oa.dedup.SparkCreateMergeRels.PROVENANCE_ACTION_CLASS; -import static eu.dnetlib.dhp.oa.dedup.SparkCreateMergeRels.hash; - public class SparkRemoveDiffRels extends AbstractSparkAction { - private static final Logger log = LoggerFactory.getLogger(SparkRemoveDiffRels.class); + private static final Logger log = LoggerFactory.getLogger(SparkRemoveDiffRels.class); - public SparkRemoveDiffRels(ArgumentApplicationParser parser, SparkSession spark) { - super(parser, spark); - } + public SparkRemoveDiffRels(ArgumentApplicationParser parser, SparkSession spark) { + super(parser, spark); + } - public static void main(String[] args) throws Exception { - ArgumentApplicationParser parser = new ArgumentApplicationParser( - IOUtils - .toString( - SparkCreateSimRels.class - .getResourceAsStream( - "/eu/dnetlib/dhp/oa/dedup/createSimRels_parameters.json"))); - parser.parseArgument(args); + public static void main(String[] args) throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCreateSimRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/createSimRels_parameters.json"))); + parser.parseArgument(args); - SparkConf conf = new SparkConf(); - new SparkCreateSimRels(parser, getSparkSession(conf)) - .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); - } + SparkConf conf = new SparkConf(); + new SparkCreateSimRels(parser, getSparkSession(conf)) + .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); + } - @Override - public void run(ISLookUpService isLookUpService) - throws DocumentException, IOException, ISLookUpException { + @Override + public void run(ISLookUpService isLookUpService) + throws DocumentException, IOException, ISLookUpException { - // read oozie parameters - final String graphBasePath = parser.get("graphBasePath"); - final String isLookUpUrl = parser.get("isLookUpUrl"); - final String actionSetId = parser.get("actionSetId"); - final String workingPath = parser.get("workingPath"); - final int numPartitions = Optional - .ofNullable(parser.get("numPartitions")) - .map(Integer::valueOf) - .orElse(NUM_PARTITIONS); + // read oozie parameters + final String graphBasePath = parser.get("graphBasePath"); + final String isLookUpUrl = parser.get("isLookUpUrl"); + final String actionSetId = parser.get("actionSetId"); + final String workingPath = parser.get("workingPath"); + final int numPartitions = Optional + .ofNullable(parser.get("numPartitions")) + .map(Integer::valueOf) + .orElse(NUM_PARTITIONS); - log.info("numPartitions: '{}'", numPartitions); - log.info("graphBasePath: '{}'", graphBasePath); - log.info("isLookUpUrl: '{}'", isLookUpUrl); - log.info("actionSetId: '{}'", actionSetId); - log.info("workingPath: '{}'", workingPath); + log.info("numPartitions: '{}'", numPartitions); + log.info("graphBasePath: '{}'", graphBasePath); + log.info("isLookUpUrl: '{}'", isLookUpUrl); + log.info("actionSetId: '{}'", actionSetId); + log.info("workingPath: '{}'", workingPath); - // for each dedup configuration - for (DedupConfig dedupConf : getConfigurations(isLookUpService, actionSetId)) { + // for each dedup configuration + for (DedupConfig dedupConf : getConfigurations(isLookUpService, actionSetId)) { - final String entity = dedupConf.getWf().getEntityType(); - final String subEntity = dedupConf.getWf().getSubEntityValue(); - log.info("Removing diffrels for: '{}'", subEntity); + final String entity = dedupConf.getWf().getEntityType(); + final String subEntity = dedupConf.getWf().getSubEntityValue(); + log.info("Removing diffrels for: '{}'", subEntity); - final String mergeRelsPath = DedupUtility.createMergeRelPath(workingPath, actionSetId, subEntity); + final String mergeRelsPath = DedupUtility.createMergeRelPath(workingPath, actionSetId, subEntity); - final String relationPath = DedupUtility.createEntityPath(graphBasePath, subEntity); + final String relationPath = DedupUtility.createEntityPath(graphBasePath, subEntity); - final int maxIterations = dedupConf.getWf().getMaxIterations(); - log.info("Max iterations {}", maxIterations); + final int maxIterations = dedupConf.getWf().getMaxIterations(); + log.info("Max iterations {}", maxIterations); - JavaRDD mergeRelsRDD = spark - .read() - .load(mergeRelsPath) - .as(Encoders.bean(Relation.class)) - .where("relClass == 'merges'") - .toJavaRDD(); + JavaRDD mergeRelsRDD = spark + .read() + .load(mergeRelsPath) + .as(Encoders.bean(Relation.class)) + .where("relClass == 'merges'") + .toJavaRDD(); - JavaRDD, String>> diffRelsRDD = spark - .read() - .textFile(relationPath) - .map(patchRelFn(), Encoders.bean(Relation.class)) - .toJavaRDD().filter(r -> filterRels(r, entity)) - .map(rel -> { - if (rel.getSource().compareTo(rel.getTarget()) < 0) - return new Tuple2<>(new Tuple2<>(rel.getSource(), rel.getTarget()), "diffRel"); - else - return new Tuple2<>(new Tuple2<>(rel.getTarget(), rel.getSource()), "diffRel"); - }); + JavaRDD, String>> diffRelsRDD = spark + .read() + .textFile(relationPath) + .map(patchRelFn(), Encoders.bean(Relation.class)) + .toJavaRDD() + .filter(r -> filterRels(r, entity)) + .map(rel -> { + if (rel.getSource().compareTo(rel.getTarget()) < 0) + return new Tuple2<>(new Tuple2<>(rel.getSource(), rel.getTarget()), "diffRel"); + else + return new Tuple2<>(new Tuple2<>(rel.getTarget(), rel.getSource()), "diffRel"); + }); - JavaRDD, String>> flatMergeRels = mergeRelsRDD - .mapToPair(rel -> new Tuple2<>(rel.getSource(), rel.getTarget())) - .groupByKey() - .flatMap(g -> { - List, String>> rels = new ArrayList<>(); + JavaRDD, String>> flatMergeRels = mergeRelsRDD + .mapToPair(rel -> new Tuple2<>(rel.getSource(), rel.getTarget())) + .groupByKey() + .flatMap(g -> { + List, String>> rels = new ArrayList<>(); - List ids = StreamSupport - .stream(g._2().spliterator(), false) - .collect(Collectors.toList()); + List ids = StreamSupport + .stream(g._2().spliterator(), false) + .collect(Collectors.toList()); - for (int i = 0; i < ids.size(); i++){ - for (int j = i+1; j < ids.size(); j++){ - if (ids.get(i).compareTo(ids.get(j)) < 0) - rels.add(new Tuple2<>(new Tuple2<>(ids.get(i), ids.get(j)), g._1())); - else - rels.add(new Tuple2<>(new Tuple2<>(ids.get(j), ids.get(i)), g._1())); - } - } - return rels.iterator(); + for (int i = 0; i < ids.size(); i++) { + for (int j = i + 1; j < ids.size(); j++) { + if (ids.get(i).compareTo(ids.get(j)) < 0) + rels.add(new Tuple2<>(new Tuple2<>(ids.get(i), ids.get(j)), g._1())); + else + rels.add(new Tuple2<>(new Tuple2<>(ids.get(j), ids.get(i)), g._1())); + } + } + return rels.iterator(); - }); + }); - JavaRDD purgedMergeRels = flatMergeRels.union(diffRelsRDD) - .mapToPair(rel -> new Tuple2<>(rel._1(), Arrays.asList(rel._2()))) - .reduceByKey((a, b) -> { - List list = new ArrayList(); - list.addAll(a); - list.addAll(b); - return list; - }) - .filter(rel -> rel._2().size() == 1) - .mapToPair(rel -> new Tuple2<>(rel._2().get(0), rel._1())) - .flatMap(rel -> { - List> rels = new ArrayList<>(); - String source = rel._1(); - rels.add(new Tuple2<>(source, rel._2()._1())); - rels.add(new Tuple2<>(source, rel._2()._2())); - return rels.iterator(); - }) - .distinct() - .flatMap(rel -> tupleToMergeRel(rel, dedupConf)); + JavaRDD purgedMergeRels = flatMergeRels + .union(diffRelsRDD) + .mapToPair(rel -> new Tuple2<>(rel._1(), Arrays.asList(rel._2()))) + .reduceByKey((a, b) -> { + List list = new ArrayList(); + list.addAll(a); + list.addAll(b); + return list; + }) + .filter(rel -> rel._2().size() == 1) + .mapToPair(rel -> new Tuple2<>(rel._2().get(0), rel._1())) + .flatMap(rel -> { + List> rels = new ArrayList<>(); + String source = rel._1(); + rels.add(new Tuple2<>(source, rel._2()._1())); + rels.add(new Tuple2<>(source, rel._2()._2())); + return rels.iterator(); + }) + .distinct() + .flatMap(rel -> tupleToMergeRel(rel, dedupConf)); - spark - .createDataset(purgedMergeRels.rdd(), Encoders.bean(Relation.class)) - .write() - .mode(SaveMode.Overwrite).parquet(mergeRelsPath); - } - } + spark + .createDataset(purgedMergeRels.rdd(), Encoders.bean(Relation.class)) + .write() + .mode(SaveMode.Overwrite) + .json(mergeRelsPath); + } + } - private static MapFunction patchRelFn() { - return value -> { - final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); - if (rel.getDataInfo() == null) { - rel.setDataInfo(new DataInfo()); - } - return rel; - }; - } + private static MapFunction patchRelFn() { + return value -> { + final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); + if (rel.getDataInfo() == null) { + rel.setDataInfo(new DataInfo()); + } + return rel; + }; + } - private boolean filterRels(Relation rel, String entityType) { + private boolean filterRels(Relation rel, String entityType) { - switch(entityType) { - case "result": - if (rel.getRelClass().equals("isDifferentFrom") && rel.getRelType().equals("resultResult") && rel.getSubRelType().equals("dedup")) - return true; - break; - case "organization": - if (rel.getRelClass().equals("isDifferentFrom") && rel.getRelType().equals("organizationOrganization") && rel.getSubRelType().equals("dedup")) - return true; - break; - default: - return false; - } - return false; - } + switch (entityType) { + case "result": + if (rel.getRelClass().equals("isDifferentFrom") && rel.getRelType().equals("resultResult") + && rel.getSubRelType().equals("dedup")) + return true; + break; + case "organization": + if (rel.getRelClass().equals("isDifferentFrom") && rel.getRelType().equals("organizationOrganization") + && rel.getSubRelType().equals("dedup")) + return true; + break; + default: + return false; + } + return false; + } - public Iterator tupleToMergeRel(Tuple2 rel, DedupConfig dedupConf) { + public Iterator tupleToMergeRel(Tuple2 rel, DedupConfig dedupConf) { - List rels = new ArrayList<>(); + List rels = new ArrayList<>(); - rels.add(rel(rel._1(), rel._2(), "merges", dedupConf)); - rels.add(rel(rel._2(), rel._1(), "isMergedIn", dedupConf)); + rels.add(rel(rel._1(), rel._2(), "merges", dedupConf)); + rels.add(rel(rel._2(), rel._1(), "isMergedIn", dedupConf)); - return rels.iterator(); - } + return rels.iterator(); + } - private Relation rel(String source, String target, String relClass, DedupConfig dedupConf) { + private Relation rel(String source, String target, String relClass, DedupConfig dedupConf) { - String entityType = dedupConf.getWf().getEntityType(); + String entityType = dedupConf.getWf().getEntityType(); - Relation r = new Relation(); - r.setSource(source); - r.setTarget(target); - r.setRelClass(relClass); - r.setRelType(entityType + entityType.substring(0, 1).toUpperCase() + entityType.substring(1)); - r.setSubRelType("dedup"); + Relation r = new Relation(); + r.setSource(source); + r.setTarget(target); + r.setRelClass(relClass); + r.setRelType(entityType + entityType.substring(0, 1).toUpperCase() + entityType.substring(1)); + r.setSubRelType("dedup"); - DataInfo info = new DataInfo(); - info.setDeletedbyinference(false); - info.setInferred(true); - info.setInvisible(false); - info.setInferenceprovenance(dedupConf.getWf().getConfigurationId()); - Qualifier provenanceAction = new Qualifier(); - provenanceAction.setClassid(PROVENANCE_ACTION_CLASS); - provenanceAction.setClassname(PROVENANCE_ACTION_CLASS); - provenanceAction.setSchemeid(DNET_PROVENANCE_ACTIONS); - provenanceAction.setSchemename(DNET_PROVENANCE_ACTIONS); - info.setProvenanceaction(provenanceAction); + DataInfo info = new DataInfo(); + info.setDeletedbyinference(false); + info.setInferred(true); + info.setInvisible(false); + info.setInferenceprovenance(dedupConf.getWf().getConfigurationId()); + Qualifier provenanceAction = new Qualifier(); + provenanceAction.setClassid(PROVENANCE_ACTION_CLASS); + provenanceAction.setClassname(PROVENANCE_ACTION_CLASS); + provenanceAction.setSchemeid(DNET_PROVENANCE_ACTIONS); + provenanceAction.setSchemename(DNET_PROVENANCE_ACTIONS); + info.setProvenanceaction(provenanceAction); - // TODO calculate the trust value based on the similarity score of the elements in the CC - // info.setTrust(); + // TODO calculate the trust value based on the similarity score of the elements in the CC + // info.setTrust(); - r.setDataInfo(info); - return r; - } + r.setDataInfo(info); + return r; + } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/copyRels_parameters.json b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/copyOpenorgsMergeRels_parameters.json similarity index 84% rename from dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/copyRels_parameters.json rename to dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/copyOpenorgsMergeRels_parameters.json index 715b0e74e..75054637f 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/copyRels_parameters.json +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/copyOpenorgsMergeRels_parameters.json @@ -18,9 +18,9 @@ "paramRequired": true }, { - "paramName": "e", - "paramLongName": "entityType", - "paramDescription": "type of the entity for the merge relations", + "paramName": "la", + "paramLongName": "isLookUpUrl", + "paramDescription": "the url for the lookup service", "paramRequired": true }, { diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml index a6b313cad..4c5505eb5 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml @@ -85,9 +85,6 @@ Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] - - - @@ -96,34 +93,6 @@ - - - yarn - cluster - Copy Merge Relations - eu.dnetlib.dhp.oa.dedup.SparkCopyRels - dhp-dedup-openaire-${projectVersion}.jar - - --executor-memory=${sparkExecutorMemory} - --executor-cores=${sparkExecutorCores} - --driver-memory=${sparkDriverMemory} - --conf spark.extraListeners=${spark2ExtraListeners} - --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} - --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} - --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} - --conf spark.sql.shuffle.partitions=3840 - - --graphBasePath${graphBasePath} - --workingPath${workingPath} - --actionSetId${actionSetId} - --entityTypeorganization - --destinationsimrel - --numPartitions8000 - - - - - yarn @@ -147,6 +116,33 @@ --workingPath${workingPath} --numPartitions8000 + + + + + + + + yarn + cluster + Copy OpenOrgs Sim Rels + eu.dnetlib.dhp.oa.dedup.SparkCopyOpenorgsSimRels + dhp-dedup-openaire-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=3840 + + --graphBasePath${graphBasePath} + --workingPath${workingPath} + --actionSetId${actionSetId} + --numPartitions8000 + diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml index d22f05ca8..998f3ac21 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml @@ -12,6 +12,10 @@ actionSetId id of the actionSet + + actionSetIdOpenorgs + id of the actionSet for OpenOrgs dedup + workingPath path for the working directory @@ -169,17 +173,17 @@ --isLookUpUrl${isLookUpUrl} --actionSetId${actionSetId} - + - - + + yarn cluster Copy Merge Relations - eu.dnetlib.dhp.oa.dedup.SparkCopyRels + eu.dnetlib.dhp.oa.dedup.SparkCopyOpenorgsMergeRels dhp-dedup-openaire-${projectVersion}.jar --executor-memory=${sparkExecutorMemory} @@ -193,15 +197,15 @@ --graphBasePath${graphBasePath} --workingPath${workingPath} - --actionSetId${actionSetId} - --entityTypeorganization - --destinationmergerel + --isLookUpUrl${isLookUpUrl} + --actionSetId${actionSetIdOpenorgs} --numPartitions8000 + yarn @@ -222,7 +226,7 @@ --graphBasePath${graphBasePath} --workingPath${workingPath} --isLookUpUrl${isLookUpUrl} - --actionSetId${actionSetId} + --actionSetId${actionSetIdOpenorgs} @@ -253,15 +257,28 @@ + - - - - - -pb - ${graphBasePath}/relation - ${dedupGraphPath}/relation - + + yarn + cluster + Update Entity + eu.dnetlib.dhp.oa.dedup.SparkCopyRelationsNoOpenorgs + dhp-dedup-openaire-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=3840 + + --graphBasePath${graphBasePath} + --workingPath${workingPath} + --dedupGraphPath${dedupGraphPath} + diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java index b6210013c..532bb43b2 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java @@ -48,6 +48,7 @@ import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.common.DbClient; import eu.dnetlib.dhp.oa.graph.raw.common.AbstractMigrationApplication; +import eu.dnetlib.dhp.oa.graph.raw.common.MigrateAction; import eu.dnetlib.dhp.oa.graph.raw.common.VerifyNsPrefixPredicate; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; import eu.dnetlib.dhp.schema.oaf.Context; @@ -76,6 +77,9 @@ public class MigrateDbEntitiesApplication extends AbstractMigrationApplication i public static final String SOURCE_TYPE = "source_type"; public static final String TARGET_TYPE = "target_type"; + private static final String ORG_ORG_RELTYPE = "organizationOrganization"; + private static final String ORG_ORG_SUBRELTYPE = "dedup"; + private final DbClient dbClient; private final long lastUpdateTimestamp; @@ -114,35 +118,53 @@ public class MigrateDbEntitiesApplication extends AbstractMigrationApplication i final Predicate verifyNamespacePrefix = new VerifyNsPrefixPredicate(nsPrefixBlacklist); - final boolean processClaims = parser.get("action") != null && parser.get("action").equalsIgnoreCase("claims"); - log.info("processClaims: {}", processClaims); + final MigrateAction process = parser.get("action") != null ? MigrateAction.valueOf(parser.get("action")) + : MigrateAction.openaire; + log.info("migrateAction: {}", process); try (final MigrateDbEntitiesApplication smdbe = new MigrateDbEntitiesApplication(hdfsPath, dbUrl, dbUser, dbPassword, isLookupUrl)) { - if (processClaims) { - log.info("Processing claims..."); - smdbe.execute("queryClaims.sql", smdbe::processClaims); - } else { - log.info("Processing datasources..."); - smdbe.execute("queryDatasources.sql", smdbe::processDatasource, verifyNamespacePrefix); - log.info("Processing projects..."); - if (dbSchema.equalsIgnoreCase("beta")) { - smdbe.execute("queryProjects.sql", smdbe::processProject, verifyNamespacePrefix); - } else { - smdbe.execute("queryProjects_production.sql", smdbe::processProject, verifyNamespacePrefix); - } + switch (process) { + case claims: + log.info("Processing claims..."); + smdbe.execute("queryClaims.sql", smdbe::processClaims); + break; + case openaire: + log.info("Processing datasources..."); + smdbe.execute("queryDatasources.sql", smdbe::processDatasource, verifyNamespacePrefix); - log.info("Processing orgs..."); - smdbe.execute("queryOrganizations.sql", smdbe::processOrganization, verifyNamespacePrefix); + log.info("Processing projects..."); + if (dbSchema.equalsIgnoreCase("beta")) { + smdbe.execute("queryProjects.sql", smdbe::processProject, verifyNamespacePrefix); + } else { + smdbe.execute("queryProjects_production.sql", smdbe::processProject, verifyNamespacePrefix); + } - log.info("Processing relationsNoRemoval ds <-> orgs ..."); - smdbe - .execute( - "queryDatasourceOrganization.sql", smdbe::processDatasourceOrganization, verifyNamespacePrefix); + log.info("Processing Organizations..."); + smdbe.execute("queryOrganizations.sql", smdbe::processOrganization, verifyNamespacePrefix); - log.info("Processing projects <-> orgs ..."); - smdbe.execute("queryProjectOrganization.sql", smdbe::processProjectOrganization, verifyNamespacePrefix); + log.info("Processing relationsNoRemoval ds <-> orgs ..."); + smdbe + .execute( + "queryDatasourceOrganization.sql", smdbe::processDatasourceOrganization, + verifyNamespacePrefix); + + log.info("Processing projects <-> orgs ..."); + smdbe + .execute( + "queryProjectOrganization.sql", smdbe::processProjectOrganization, verifyNamespacePrefix); + break; + case openorgs: + log.info("Processing Openorgs..."); + smdbe + .execute( + "queryOrganizationsFromOpenOrgsDB.sql", smdbe::processOrganization, verifyNamespacePrefix); + + log.info("Processing Openorgs Merge Rels..."); + smdbe.execute("querySimilarityFromOpenOrgsDB.sql", smdbe::processOrgOrgSimRels); + + break; } log.info("All done."); } @@ -585,6 +607,43 @@ public class MigrateDbEntitiesApplication extends AbstractMigrationApplication i } } + public List processOrgOrgSimRels(final ResultSet rs) { + try { + final DataInfo info = prepareDataInfo(rs); // TODO + + final String orgId1 = createOpenaireId(20, rs.getString("id1"), true); + final String orgId2 = createOpenaireId(40, rs.getString("id2"), true); + final String relClass = rs.getString("relclass"); + + final List collectedFrom = listKeyValues( + createOpenaireId(10, rs.getString("collectedfromid"), true), rs.getString("collectedfromname")); + + final Relation r1 = new Relation(); + r1.setRelType(ORG_ORG_RELTYPE); + r1.setSubRelType(ORG_ORG_SUBRELTYPE); + r1.setRelClass(relClass); + r1.setSource(orgId1); + r1.setTarget(orgId2); + r1.setCollectedfrom(collectedFrom); + r1.setDataInfo(info); + r1.setLastupdatetimestamp(lastUpdateTimestamp); + + final Relation r2 = new Relation(); + r2.setRelType(ORG_ORG_RELTYPE); + r2.setSubRelType(ORG_ORG_SUBRELTYPE); + r2.setRelClass(relClass); + r2.setSource(orgId2); + r2.setTarget(orgId1); + r2.setCollectedfrom(collectedFrom); + r2.setDataInfo(info); + r2.setLastupdatetimestamp(lastUpdateTimestamp); + + return Arrays.asList(r1, r2); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + @Override public void close() throws IOException { super.close(); diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/MigrateAction.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/MigrateAction.java new file mode 100644 index 000000000..d9ee9bb6a --- /dev/null +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/MigrateAction.java @@ -0,0 +1,9 @@ + +package eu.dnetlib.dhp.oa.graph.raw.common; + +//enum to specify the different actions available for the MigrateDbEntitiesApplication job +public enum MigrateAction { + claims, // migrate claims to the raw graph + openorgs, // migrate organizations from openorgs to the raw graph + openaire // migrate openaire entities to the raw graph +} diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_all/oozie_app/workflow.xml b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_all/oozie_app/workflow.xml index d8146d9a2..adaee65d3 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_all/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_all/oozie_app/workflow.xml @@ -25,6 +25,18 @@ postgresPassword the password postgres + + + postgresOpenOrgsURL + the postgres URL to access to the OpenOrgs database + + + postgresOpenOrgsUser + the user of OpenOrgs database + + + postgresOpenOrgsPassword + the password of OpenOrgs database dbSchema @@ -178,14 +190,34 @@ - + eu.dnetlib.dhp.oa.graph.raw.MigrateDbEntitiesApplication - --hdfsPath${contentPath}/db_records + --hdfsPath${contentPath}/db_openaire --postgresUrl${postgresURL} --postgresUser${postgresUser} --postgresPassword${postgresPassword} --isLookupUrl${isLookupUrl} + --actionopenaire + --dbschema${dbSchema} + --nsPrefixBlacklist${nsPrefixBlacklist} + + + + + + + + + + + eu.dnetlib.dhp.oa.graph.raw.MigrateDbEntitiesApplication + --hdfsPath${contentPath}/db_openorgs + --postgresUrl${postgresOpenOrgsURL} + --postgresUser${postgresOpenOrgsUser} + --postgresPassword${postgresOpenOrgsPassword} + --isLookupUrl${isLookupUrl} + --actionopenorgs --dbschema${dbSchema} --nsPrefixBlacklist${nsPrefixBlacklist} @@ -314,7 +346,7 @@ --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} - --sourcePaths${contentPath}/db_records,${contentPath}/oaf_records,${contentPath}/odf_records + --sourcePaths${contentPath}/db_openaire,${contentPath}/db_openorgs,${contentPath}/oaf_records,${contentPath}/odf_records --targetPath${workingDir}/entities --isLookupUrl${isLookupUrl} diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizationsFromOpenOrgsDB.sql b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizationsFromOpenOrgsDB.sql index 3396f365c..82ece5a1c 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizationsFromOpenOrgsDB.sql +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizationsFromOpenOrgsDB.sql @@ -4,6 +4,8 @@ SELECT o.name AS legalname, array_agg(DISTINCT n.name) AS "alternativeNames", (array_agg(u.url))[1] AS websiteurl, + '' AS logourl, + o.creation_date AS dateofcollection, o.modification_date AS dateoftransformation, false AS inferred, false AS deletedbyinference, @@ -13,7 +15,17 @@ SELECT 'OpenOrgs Database' AS collectedfromname, o.country || '@@@dnet:countries' AS country, 'sysimport:crosswalk:entityregistry@@@dnet:provenance_actions' AS provenanceaction, - array_agg(DISTINCT i.otherid || '###' || i.type || '@@@dnet:pid_types') AS pid + array_agg(DISTINCT i.otherid || '###' || i.type || '@@@dnet:pid_types') AS pid, + null AS eclegalbody, + null AS eclegalperson, + null AS ecnonprofit, + null AS ecresearchorganization, + null AS echighereducation, + null AS ecinternationalorganizationeurinterests, + null AS ecinternationalorganization, + null AS ecenterprise, + null AS ecsmevalidated, + null AS ecnutscode FROM organizations o LEFT OUTER JOIN acronyms a ON (a.id = o.id) LEFT OUTER JOIN urls u ON (u.id = o.id) @@ -22,6 +34,7 @@ FROM organizations o GROUP BY o.id, o.name, + o.creation_date, o.modification_date, o.country @@ -33,6 +46,8 @@ SELECT n.name AS legalname, ARRAY[]::text[] AS "alternativeNames", (array_agg(u.url))[1] AS websiteurl, + '' AS logourl, + o.creation_date AS dateofcollection, o.modification_date AS dateoftransformation, false AS inferred, false AS deletedbyinference, @@ -42,12 +57,24 @@ SELECT 'OpenOrgs Database' AS collectedfromname, o.country || '@@@dnet:countries' AS country, 'sysimport:crosswalk:entityregistry@@@dnet:provenance_actions' AS provenanceaction, - array_agg(DISTINCT i.otherid || '###' || i.type || '@@@dnet:pid_types') AS pid + array_agg(DISTINCT i.otherid || '###' || i.type || '@@@dnet:pid_types') AS pid, + null AS eclegalbody, + null AS eclegalperson, + null AS ecnonprofit, + null AS ecresearchorganization, + null AS echighereducation, + null AS ecinternationalorganizationeurinterests, + null AS ecinternationalorganization, + null AS ecenterprise, + null AS ecsmevalidated, + null AS ecnutscode FROM other_names n LEFT OUTER JOIN organizations o ON (n.id = o.id) LEFT OUTER JOIN urls u ON (u.id = o.id) LEFT OUTER JOIN other_ids i ON (i.id = o.id) GROUP BY - o.id, o.modification_date, o.country, n.name - - + o.id, + o.creation_date, + o.modification_date, + o.country, + n.name; \ No newline at end of file diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/querySimilarityFromOpenOrgsDB.sql b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/querySimilarityFromOpenOrgsDB.sql index 4407559c6..138bf6a96 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/querySimilarityFromOpenOrgsDB.sql +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/querySimilarityFromOpenOrgsDB.sql @@ -1,17 +1,47 @@ -SELECT local_id AS id1, oa_original_id AS id2 FROM openaire_simrels WHERE reltype = 'is_similar' +-- relations approved by the user +SELECT + local_id AS id1, + oa_original_id AS id2, + 'openaire____::openorgs' AS collectedfromid, + 'OpenOrgs Database' AS collectedfromname, + false AS inferred, + false AS deletedbyinference, + 0.99 AS trust, + '' AS inferenceprovenance, + 'isSimilarTo' AS relclass +FROM oa_duplicates WHERE reltype = 'is_similar' UNION ALL +-- relations between openorgs and mesh (alternative names) SELECT - o.id AS id1, - 'openorgsmesh'||substring(o.id, 13)||'-'||md5(a.acronym) AS id2 -FROM acronyms a - LEFT OUTER JOIN organizations o ON (a.id = o.id) - -UNION ALL - -SELECT - o.id AS id1, - 'openorgsmesh'||substring(o.id, 13)||'-'||md5(n.name) AS id2 + o.id AS id1, + 'openorgsmesh'||substring(o.id, 13)||'-'||md5(n.name) AS id2, + 'openaire____::openorgs' AS collectedfromid, + 'OpenOrgs Database' AS collectedfromname, + false AS inferred, + false AS deletedbyinference, + 0.99 AS trust, + '' AS inferenceprovenance, + 'isSimilarTo' AS relclass FROM other_names n LEFT OUTER JOIN organizations o ON (n.id = o.id) + +UNION ALL + +-- diff relations approved by the user +SELECT + local_id AS id1, + oa_original_id AS id2, + 'openaire____::openorgs' AS collectedfromid, + 'OpenOrgs Database' AS collectedfromname, + false AS inferred, + false AS deletedbyinference, + 0.99 AS trust, + '' AS inferenceprovenance, + 'isDifferentFrom' AS relclass +FROM oa_duplicates WHERE reltype = 'is_different' + + +--TODO ??? +--Creare relazioni isDifferentFrom anche tra i suggerimenti: (A is_similar B) and (A is_different C) => (B is_different C) \ No newline at end of file From ebcc3ec14f597651dea04469bf645621e5aa148a Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Thu, 11 Feb 2021 16:25:51 +0100 Subject: [PATCH 107/445] updated wrong datacite identifier in trasformation --- .../actionmanager/datacite/DataciteToOAFTransformation.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala index 933f1445f..dc5b8b093 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala @@ -66,7 +66,7 @@ object DataciteToOAFTransformation { val unknown_repository: HostedByMapType = HostedByMapType("openaire____::1256f046-bf1f-4afc-8b47-d0b147148b18", "Unknown Repository", "Unknown Repository", Some(1.0F)) val dataInfo: DataInfo = generateDataInfo("0.9") - val DATACITE_COLLECTED_FROM: KeyValue = OafMapperUtils.keyValue("openaire____::datacite", "Datacite") + val DATACITE_COLLECTED_FROM: KeyValue = OafMapperUtils.keyValue("openaire____::9e3be59865b2c1c335d32dae2fe7b254", "Datacite") val hostedByMap: Map[String, HostedByMapType] = { val s = Source.fromInputStream(getClass.getResourceAsStream("hostedBy_map.json")).mkString From 17e6f1934edb0eab39a74173a443c58184272776 Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Fri, 12 Feb 2021 11:48:11 +0100 Subject: [PATCH 108/445] fixed NPE on cleaner --- .../datacite/AbstractRestClient.scala | 14 +- .../dhp/transformation/xslt/Cleaner.java | 6 +- .../transformation/TransformationJobTest.java | 12 +- .../eu/dnetlib/dhp/transform/ext_simple.xsl | 1 + .../eu/dnetlib/dhp/transform/input.xml | 2 +- .../eu/dnetlib/dhp/transform/input_zenodo.xml | 99 ++++ .../eu/dnetlib/dhp/transform/zenodo_tr.xslt | 444 ++++++++++++++++++ 7 files changed, 571 insertions(+), 7 deletions(-) create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input_zenodo.xml create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/zenodo_tr.xslt diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/AbstractRestClient.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/AbstractRestClient.scala index 852147ccd..3c7770075 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/AbstractRestClient.scala +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/AbstractRestClient.scala @@ -53,11 +53,21 @@ abstract class AbstractRestClient extends Iterator[String]{ } + + private def doHTTPRequest[A <: HttpUriRequest](r: A) :String ={ val client = HttpClients.createDefault try { - val response = client.execute(r) - IOUtils.toString(response.getEntity.getContent) + var tries = 4 + while (tries > 0) { + val response = client.execute(r) + if (response.getStatusLine.getStatusCode > 400) { + tries -= 1 + } + else + return IOUtils.toString(response.getEntity.getContent) + } + "" } catch { case e: Throwable => throw new RuntimeException("Error on executing request ", e) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java index 7b0fdd484..8b7024cbe 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java @@ -27,13 +27,17 @@ public class Cleaner implements ExtensionFunction, Serializable { @Override public SequenceType[] getArgumentTypes() { return new SequenceType[] { - SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ONE), + SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ZERO_OR_MORE), SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ONE) }; } @Override public XdmValue call(XdmValue[] xdmValues) throws SaxonApiException { + XdmValue r = xdmValues[0]; + if (r.size() == 0){ + return new XdmAtomicValue(""); + } final String currentValue = xdmValues[0].itemAt(0).getStringValue(); final String vocabularyName = xdmValues[1].itemAt(0).getStringValue(); Qualifier cleanedValue = vocabularies.getSynonymAsQualifier(vocabularyName, currentValue); diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java index 997727e33..69b31b30f 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java @@ -61,13 +61,19 @@ public class TransformationJobTest extends AbstractVocabularyTest { // We Set the input Record getting the XML from the classpath final MetadataRecord mr = new MetadataRecord(); - mr.setBody(IOUtils.toString(getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/input.xml"))); + mr.setBody(IOUtils.toString(getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/input_zenodo.xml"))); // We Load the XSLT transformation Rule from the classpath - XSLTTransformationFunction tr = loadTransformationRule("/eu/dnetlib/dhp/transform/ext_simple.xsl"); + XSLTTransformationFunction tr = loadTransformationRule("/eu/dnetlib/dhp/transform/zenodo_tr.xslt"); + + + MetadataRecord result = tr.call(mr); + + + // Print the record - System.out.println(tr.call(mr).getBody()); + System.out.println(result.getBody()); // TODO Create significant Assert } diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl index becd3a05e..c114217c2 100644 --- a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl @@ -6,6 +6,7 @@ version="2.0" exclude-result-prefixes="xsl vocabulary"> + diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input.xml b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input.xml index ebe8e919b..3d136d56d 100644 --- a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input.xml +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input.xml @@ -6,7 +6,7 @@ PSNCRepository:PSNCExternalRepository:Departments PSNCRepository:PSNCExternalRepository:Departments:NetworkServices PSNCRepository:PSNCExternalRepository - PSNCRepository:PSNCExternalRepository:publications + aRTIcle - Letter to the editor PSNCRepository diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input_zenodo.xml b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input_zenodo.xml new file mode 100644 index 000000000..043eae343 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input_zenodo.xml @@ -0,0 +1,99 @@ + + + + r37b0ad08687::000374d100a9db469bd42b69dbb40b36 + 10.5281/zenodo.3234526 + 2020-03-23T03:03:50.72Z + r37b0ad08687 + oai:zenodo.org:3234526 + 2020-03-19T10:58:08Z + openaire_data + user-epfl + + + + true + 3.1 + CERN.ZENODO + + + 10.5281/zenodo.3234526 + + + Nouchi, Vincent + Physics of Aquatic Systems Laboratory (APHYS) – Margaretha Kamprad Chair, ENAC, EPFL, Lausanne, 1015, Switzerland + + + Lavanchy, Sébastien + Physics of Aquatic Systems Laboratory (APHYS) – Margaretha Kamprad Chair, ENAC, EPFL, Lausanne, 1015, Switzerland + + + Baracchini, Theo + Physics of Aquatic Systems Laboratory (APHYS) – Margaretha Kamprad Chair, ENAC, EPFL, Lausanne, 1015, Switzerland + + + Wüest, Alfred + Physics of Aquatic Systems Laboratory (APHYS) – Margaretha Kamprad Chair, ENAC, EPFL, Lausanne, 1015, Switzerland + + + Bouffard, Damien + Eawag, Swiss Federal Institute of Aquatic Science and Technology, Surface Waters – Research and Management, Kastanienbaum, 6047, Switzerland + + + + Temperature and ADCP data collected on Lake Geneva between 2015 and 2017 + + Zenodo + 2019 + + Lake Geneva + temperature + ADCP + + + 2019-05-29 + + + + 10.5281/zenodo.3234525 + https://zenodo.org/communities/epfl + + 1.0.0 + + Creative Commons Attribution 4.0 International + Open Access + + +

Data collected between 2015 and 2017 on Lake Geneva by Acoustic Doppler Current Profiler (ADCP) and CTDs. One file includes all the temperature profiles, the two others are the ADCP data (up- and down-looking) at the SHL2 station (centre of the main basin). Coordinates of the SHL2 station are 534700 and 144950 in the Swiss CH1903 coordinate system. The file with the CTD data contains the coordinates of the sample location (lat, lon), times (in MATLAB time), depths (in meters) and temperatures (in &deg;C).

+ +

All files are in MATLAB .mat format.

+
+
+
+
+
+ + + + https%3A%2F%2Fzenodo.org%2Foai2d + oai:zenodo.org:3234526 + 2020-03-19T10:58:08Z + + + + + false + false + 0.9 + + + + +
\ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/zenodo_tr.xslt b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/zenodo_tr.xslt new file mode 100644 index 000000000..e67ed9dda --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/zenodo_tr.xslt @@ -0,0 +1,444 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OPEN + + + + + CLOSED + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 29c6f7e255cd6b23d254833789827f8b6c869a73 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Fri, 12 Feb 2021 12:31:02 +0100 Subject: [PATCH 109/445] classes related to the collection workflow moved into common package; implemented MongoDB collection plugins --- .../eu/dnetlib/dhp/message/MessageSender.java | 8 +- dhp-workflows/dhp-aggregation/pom.xml | 5 +- .../actionmanager/project/utils/ReadCSV.java | 2 +- .../project/utils/ReadExcel.java | 2 +- .../{worker => }/CollectorException.java | 2 +- .../{worker => }/CollectorPluginReport.java | 9 +- .../{worker => }/CollectorWorker.java | 46 +++++-- .../CollectorWorkerApplication.java | 32 +++-- .../{worker => }/CollectorWorkerReporter.java | 2 +- .../{worker => }/HttpClientParams.java | 2 +- .../{worker => }/HttpConnector2.java | 2 +- .../UnknownCollectorPluginException.java | 2 +- .../collection/{worker => }/XmlCleaner.java | 2 +- .../collection/plugin/CollectorPlugin.java | 4 +- .../mongodb/MongoDbCollectorPlugin.java | 59 ++++++++ .../mongodb/MongoDbDumpCollectorPlugin.java | 54 ++++++++ .../plugin/oai/OaiCollectorPlugin.java | 6 +- .../collection/plugin/oai/OaiIterator.java | 8 +- .../plugin/oai/OaiIteratorFactory.java | 6 +- .../worker/CollectorPluginFactory.java | 20 --- .../dhp/collection/oozie_app/workflow.xml | 4 +- .../project/EXCELParserTest.java | 4 +- .../dhp/collection/CollectionJobTest.java | 130 ------------------ .../collection/CollectionWorkflowTest.java | 113 +++++++++++++++ .../GenerateNativeStoreSparkJobTest.java} | 84 +++++++++-- .../CollectorWorkerApplicationTests.java | 10 -- .../utils/CollectorPluginReportTest.java | 2 +- .../transformation/TransformationJobTest.java | 4 +- .../dnetlib/dhp/collection/apiDescriptor.json | 10 ++ .../eu/dnetlib/dhp/oa/provision/fields.xml | 2 + .../eu/dnetlib/dhp/oa/provision/record.xml | 4 +- 31 files changed, 411 insertions(+), 229 deletions(-) rename dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/{worker => }/CollectorException.java (93%) rename dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/{worker => }/CollectorPluginReport.java (90%) rename dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/{worker => }/CollectorWorker.java (64%) rename dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/{worker => }/CollectorWorkerApplication.java (86%) rename dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/{worker => }/CollectorWorkerReporter.java (97%) rename dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/{worker => }/HttpClientParams.java (97%) rename dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/{worker => }/HttpConnector2.java (99%) rename dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/{worker => }/UnknownCollectorPluginException.java (94%) rename dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/{worker => }/XmlCleaner.java (99%) create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MongoDbCollectorPlugin.java create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MongoDbDumpCollectorPlugin.java delete mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorPluginFactory.java delete mode 100644 dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/CollectionJobTest.java create mode 100644 dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/CollectionWorkflowTest.java rename dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/{aggregation/AggregationJobTest.java => collection/GenerateNativeStoreSparkJobTest.java} (73%) create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/apiDescriptor.json diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageSender.java b/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageSender.java index 3f9d07a7e..16bb0c97e 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageSender.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageSender.java @@ -1,6 +1,9 @@ package eu.dnetlib.dhp.message; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPut; @@ -30,13 +33,15 @@ public class MessageSender { private final String workflowId; + private ExecutorService executorService = Executors.newCachedThreadPool(); + public MessageSender(final String dnetMessageEndpoint, final String workflowId) { this.workflowId = workflowId; this.dnetMessageEndpoint = dnetMessageEndpoint; } public void sendMessage(final Message message) { - new Thread(() -> _sendMessage(message)).start(); + executorService.submit(() -> _sendMessage(message)); } public void sendMessage(final Long current, final Long total) { @@ -67,7 +72,6 @@ public class MessageSender { .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT_MS) .setSocketTimeout(SOCKET_TIMEOUT_MS) .build(); - ; try (final CloseableHttpClient client = HttpClients .custom() diff --git a/dhp-workflows/dhp-aggregation/pom.xml b/dhp-workflows/dhp-aggregation/pom.xml index f0ee42542..6887be55e 100644 --- a/dhp-workflows/dhp-aggregation/pom.xml +++ b/dhp-workflows/dhp-aggregation/pom.xml @@ -106,7 +106,10 @@ commons-compress - + + org.mongodb + mongo-java-driver + diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java index 3f64eb953..cad6b94e1 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadCSV.java @@ -18,7 +18,7 @@ import org.apache.hadoop.fs.Path; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.collection.worker.HttpConnector2; +import eu.dnetlib.dhp.collection.HttpConnector2; /** * Applies the parsing of a csv file and writes the Serialization of it in hdfs diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java index c661909b0..fc3b38ac5 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/project/utils/ReadExcel.java @@ -15,7 +15,7 @@ import org.apache.hadoop.fs.Path; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.collection.worker.HttpConnector2; +import eu.dnetlib.dhp.collection.HttpConnector2; /** * Applies the parsing of an excel file and writes the Serialization of it in hdfs diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorException.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorException.java similarity index 93% rename from dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorException.java rename to dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorException.java index 71d225f13..144d297e6 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorException.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorException.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.collection.worker; +package eu.dnetlib.dhp.collection; public class CollectorException extends Exception { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorPluginReport.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorPluginReport.java similarity index 90% rename from dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorPluginReport.java rename to dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorPluginReport.java index 2da6ac8f9..a7204523a 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorPluginReport.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorPluginReport.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.collection.worker; +package eu.dnetlib.dhp.collection; import static eu.dnetlib.dhp.utils.DHPUtils.*; @@ -17,15 +17,10 @@ import org.slf4j.LoggerFactory; import com.fasterxml.jackson.annotation.JsonIgnore; -import eu.dnetlib.dhp.application.ApplicationUtils; - public class CollectorPluginReport extends LinkedHashMap implements Closeable { private static final Logger log = LoggerFactory.getLogger(CollectorPluginReport.class); - @JsonIgnore - private FileSystem fs; - @JsonIgnore private Path path; @@ -38,9 +33,7 @@ public class CollectorPluginReport extends LinkedHashMap impleme } public CollectorPluginReport(FileSystem fs, Path path) throws IOException { - this.fs = fs; this.path = path; - this.fos = fs.create(path); } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java similarity index 64% rename from dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java rename to dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java index 945eff8b0..ace725bfd 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorker.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java @@ -1,23 +1,27 @@ -package eu.dnetlib.dhp.collection.worker; +package eu.dnetlib.dhp.collection; import static eu.dnetlib.dhp.common.Constants.SEQUENCE_FILE_NAME; import java.io.IOException; +import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; -import org.apache.hadoop.conf.Configuration; +import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.SequenceFile; import org.apache.hadoop.io.Text; -import org.apache.hadoop.io.compress.GzipCodec; +import org.apache.hadoop.io.compress.DeflateCodec; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; -import eu.dnetlib.dhp.collection.ApiDescriptor; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; +import eu.dnetlib.dhp.collection.plugin.mongodb.MongoDbCollectorPlugin; +import eu.dnetlib.dhp.collection.plugin.mongodb.MongoDbDumpCollectorPlugin; +import eu.dnetlib.dhp.collection.plugin.oai.OaiCollectorPlugin; import eu.dnetlib.dhp.message.MessageSender; public class CollectorWorker { @@ -26,7 +30,7 @@ public class CollectorWorker { private final ApiDescriptor api; - private final Configuration conf; + private final FileSystem fileSystem; private final MDStoreVersion mdStoreVersion; @@ -38,13 +42,13 @@ public class CollectorWorker { public CollectorWorker( final ApiDescriptor api, - final Configuration conf, + final FileSystem fileSystem, final MDStoreVersion mdStoreVersion, final HttpClientParams clientParams, final MessageSender messageSender, final CollectorPluginReport report) { this.api = api; - this.conf = conf; + this.fileSystem = fileSystem; this.mdStoreVersion = mdStoreVersion; this.clientParams = clientParams; this.messageSender = messageSender; @@ -56,16 +60,16 @@ public class CollectorWorker { final String outputPath = mdStoreVersion.getHdfsPath() + SEQUENCE_FILE_NAME; log.info("outputPath path is {}", outputPath); - final CollectorPlugin plugin = CollectorPluginFactory.getPluginByProtocol(clientParams, api.getProtocol()); + final CollectorPlugin plugin = getCollectorPlugin(); final AtomicInteger counter = new AtomicInteger(0); try (SequenceFile.Writer writer = SequenceFile .createWriter( - conf, + fileSystem.getConf(), SequenceFile.Writer.file(new Path(outputPath)), SequenceFile.Writer.keyClass(IntWritable.class), SequenceFile.Writer.valueClass(Text.class), - SequenceFile.Writer.compression(SequenceFile.CompressionType.BLOCK, new GzipCodec()))) { + SequenceFile.Writer.compression(SequenceFile.CompressionType.BLOCK, new DeflateCodec()))) { final IntWritable key = new IntWritable(counter.get()); final Text value = new Text(); plugin @@ -94,4 +98,26 @@ public class CollectorWorker { } } + private CollectorPlugin getCollectorPlugin() throws UnknownCollectorPluginException { + switch (StringUtils.lowerCase(StringUtils.trim(api.getProtocol()))) { + case "oai": + return new OaiCollectorPlugin(clientParams); + case "other": + final String plugin = Optional + .ofNullable(api.getParams().get("other_plugin_type")) + .orElseThrow(() -> new UnknownCollectorPluginException("other_plugin_type")); + + switch (plugin) { + case "mdstore_mongodb_dump": + return new MongoDbDumpCollectorPlugin(fileSystem); + case "mdstore_mongodb": + return new MongoDbCollectorPlugin(); + default: + throw new UnknownCollectorPluginException("Unknown plugin type: " + plugin); + } + default: + throw new UnknownCollectorPluginException("Unknown protocol: " + api.getProtocol()); + } + } + } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorkerApplication.java similarity index 86% rename from dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java rename to dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorkerApplication.java index 17f09ee5a..0eea0837c 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerApplication.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorkerApplication.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.collection.worker; +package eu.dnetlib.dhp.collection; import static eu.dnetlib.dhp.common.Constants.*; import static eu.dnetlib.dhp.utils.DHPUtils.*; @@ -9,7 +9,6 @@ import java.util.Optional; import org.apache.commons.cli.ParseException; import org.apache.commons.io.IOUtils; -import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.slf4j.Logger; @@ -17,7 +16,6 @@ import org.slf4j.LoggerFactory; import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.collection.ApiDescriptor; import eu.dnetlib.dhp.message.MessageSender; /** @@ -32,6 +30,12 @@ public class CollectorWorkerApplication { private static final Logger log = LoggerFactory.getLogger(CollectorWorkerApplication.class); + private FileSystem fileSystem; + + public CollectorWorkerApplication(FileSystem fileSystem) { + this.fileSystem = fileSystem; + } + /** * @param args */ @@ -63,6 +67,18 @@ public class CollectorWorkerApplication { final String workflowId = argumentParser.get("workflowId"); log.info("workflowId is {}", workflowId); + final HttpClientParams clientParams = getClientParams(argumentParser); + + final ApiDescriptor api = MAPPER.readValue(apiDescriptor, ApiDescriptor.class); + final FileSystem fileSystem = FileSystem.get(getHadoopConfiguration(hdfsuri)); + + new CollectorWorkerApplication(fileSystem) + .run(mdStoreVersion, clientParams, api, dnetMessageManagerURL, workflowId); + } + + protected void run(String mdStoreVersion, HttpClientParams clientParams, ApiDescriptor api, + String dnetMessageManagerURL, String workflowId) throws IOException { + final MessageSender ms = new MessageSender(dnetMessageManagerURL, workflowId); final MDStoreVersion currentVersion = MAPPER.readValue(mdStoreVersion, MDStoreVersion.class); @@ -70,13 +86,9 @@ public class CollectorWorkerApplication { final String reportPath = currentVersion.getHdfsPath() + REPORT_FILE_NAME; log.info("report path is {}", reportPath); - final HttpClientParams clientParams = getClientParams(argumentParser); - - final ApiDescriptor api = MAPPER.readValue(apiDescriptor, ApiDescriptor.class); - final Configuration conf = getHadoopConfiguration(hdfsuri); - - try (CollectorPluginReport report = new CollectorPluginReport(FileSystem.get(conf), new Path(reportPath))) { - final CollectorWorker worker = new CollectorWorker(api, conf, currentVersion, clientParams, ms, report); + try (CollectorPluginReport report = new CollectorPluginReport(fileSystem, new Path(reportPath))) { + final CollectorWorker worker = new CollectorWorker(api, fileSystem, currentVersion, clientParams, ms, + report); worker.collect(); report.setSuccess(true); } catch (Throwable e) { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerReporter.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorkerReporter.java similarity index 97% rename from dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerReporter.java rename to dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorkerReporter.java index 3f6fc4784..d8cf3ec02 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorWorkerReporter.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorkerReporter.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.collection.worker; +package eu.dnetlib.dhp.collection; import static eu.dnetlib.dhp.common.Constants.REPORT_FILE_NAME; import static eu.dnetlib.dhp.utils.DHPUtils.*; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpClientParams.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/HttpClientParams.java similarity index 97% rename from dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpClientParams.java rename to dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/HttpClientParams.java index f45790460..ab0d5cc02 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpClientParams.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/HttpClientParams.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.collection.worker; +package eu.dnetlib.dhp.collection; /** * Bundles the http connection parameters driving the client behaviour. diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpConnector2.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/HttpConnector2.java similarity index 99% rename from dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpConnector2.java rename to dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/HttpConnector2.java index 368c89509..72a2a70a2 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/HttpConnector2.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/HttpConnector2.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.collection.worker; +package eu.dnetlib.dhp.collection; import static eu.dnetlib.dhp.utils.DHPUtils.*; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/UnknownCollectorPluginException.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/UnknownCollectorPluginException.java similarity index 94% rename from dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/UnknownCollectorPluginException.java rename to dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/UnknownCollectorPluginException.java index 7134dd069..2b0a98e53 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/UnknownCollectorPluginException.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/UnknownCollectorPluginException.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.collection.worker; +package eu.dnetlib.dhp.collection; public class UnknownCollectorPluginException extends Exception { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/XmlCleaner.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/XmlCleaner.java similarity index 99% rename from dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/XmlCleaner.java rename to dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/XmlCleaner.java index 41ba02196..c674031f6 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/XmlCleaner.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/XmlCleaner.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.collection.worker; +package eu.dnetlib.dhp.collection; import java.util.HashMap; import java.util.HashSet; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java index e2be481ed..0a4b3a892 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java @@ -4,8 +4,8 @@ package eu.dnetlib.dhp.collection.plugin; import java.util.stream.Stream; import eu.dnetlib.dhp.collection.ApiDescriptor; -import eu.dnetlib.dhp.collection.worker.CollectorException; -import eu.dnetlib.dhp.collection.worker.CollectorPluginReport; +import eu.dnetlib.dhp.collection.CollectorException; +import eu.dnetlib.dhp.collection.CollectorPluginReport; public interface CollectorPlugin { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MongoDbCollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MongoDbCollectorPlugin.java new file mode 100644 index 000000000..7d1952f9c --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MongoDbCollectorPlugin.java @@ -0,0 +1,59 @@ + +package eu.dnetlib.dhp.collection.plugin.mongodb; + +import java.util.Optional; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.bson.Document; + +import com.mongodb.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; + +import eu.dnetlib.dhp.collection.ApiDescriptor; +import eu.dnetlib.dhp.collection.CollectorException; +import eu.dnetlib.dhp.collection.CollectorPluginReport; +import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; + +public class MongoDbCollectorPlugin implements CollectorPlugin { + + public static final String MONGODB_HOST = "mongodb_host"; + public static final String MONGODB_PORT = "mongodb_port"; + public static final String MONGODB_COLLECTION = "mongodb_collection"; + public static final String MONGODB_DBNAME = "mongodb_dbname"; + + @Override + public Stream collect(ApiDescriptor api, CollectorPluginReport report) throws CollectorException { + + final String host = Optional + .ofNullable(api.getParams().get(MONGODB_HOST)) + .orElseThrow(() -> new CollectorException(String.format("missing parameter '%s'", MONGODB_HOST))); + + final Integer port = Optional + .ofNullable(api.getParams().get(MONGODB_PORT)) + .map(Integer::parseInt) + .orElseThrow(() -> new CollectorException(String.format("missing parameter '%s'", MONGODB_PORT))); + + final String dbName = Optional + .ofNullable(api.getParams().get(MONGODB_DBNAME)) + .orElseThrow(() -> new CollectorException(String.format("missing parameter '%s'", MONGODB_DBNAME))); + + final String collection = Optional + .ofNullable(api.getParams().get(MONGODB_COLLECTION)) + .orElseThrow(() -> new CollectorException(String.format("missing parameter '%s'", MONGODB_COLLECTION))); + + final MongoClient mongoClient = new MongoClient(host, port); + final MongoDatabase database = mongoClient.getDatabase(dbName); + final MongoCollection mdstore = database.getCollection(collection); + + long size = mdstore.count(); + + return StreamSupport + .stream( + Spliterators.spliterator(mdstore.find().iterator(), size, Spliterator.SIZED), false) + .map(doc -> doc.getString("body")); + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MongoDbDumpCollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MongoDbDumpCollectorPlugin.java new file mode 100644 index 000000000..d08732593 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MongoDbDumpCollectorPlugin.java @@ -0,0 +1,54 @@ + +package eu.dnetlib.dhp.collection.plugin.mongodb; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.Optional; +import java.util.stream.Stream; +import java.util.zip.GZIPInputStream; + +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; + +import eu.dnetlib.dhp.collection.ApiDescriptor; +import eu.dnetlib.dhp.collection.CollectorException; +import eu.dnetlib.dhp.collection.CollectorPluginReport; +import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; +import eu.dnetlib.dhp.utils.DHPUtils; + +public class MongoDbDumpCollectorPlugin implements CollectorPlugin { + + public static final String PATH_PARAM = "path"; + public static final String BODY_JSONPATH = "$.body"; + + public FileSystem fileSystem; + + public MongoDbDumpCollectorPlugin(FileSystem fileSystem) { + this.fileSystem = fileSystem; + } + + @Override + public Stream collect(ApiDescriptor api, CollectorPluginReport report) throws CollectorException { + + final Path path = Optional + .ofNullable(api.getParams().get("path")) + .map(Path::new) + .orElseThrow(() -> new CollectorException(String.format("missing parameter '%s'", PATH_PARAM))); + + try { + if (!fileSystem.exists(path)) { + throw new CollectorException("path does not exist: " + path.toString()); + } + + return new BufferedReader( + new InputStreamReader(new GZIPInputStream(fileSystem.open(path)), Charset.defaultCharset())) + .lines() + .map(s -> DHPUtils.getJPathString(BODY_JSONPATH, s)); + + } catch (IOException e) { + throw new CollectorException(e); + } + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java index 84228abf4..8efdeb838 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java @@ -14,10 +14,10 @@ import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import eu.dnetlib.dhp.collection.ApiDescriptor; +import eu.dnetlib.dhp.collection.CollectorException; +import eu.dnetlib.dhp.collection.CollectorPluginReport; +import eu.dnetlib.dhp.collection.HttpClientParams; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; -import eu.dnetlib.dhp.collection.worker.CollectorException; -import eu.dnetlib.dhp.collection.worker.CollectorPluginReport; -import eu.dnetlib.dhp.collection.worker.HttpClientParams; public class OaiCollectorPlugin implements CollectorPlugin { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java index 8d913b68f..edfcb7bb5 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java @@ -16,10 +16,10 @@ import org.dom4j.io.SAXReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import eu.dnetlib.dhp.collection.worker.CollectorException; -import eu.dnetlib.dhp.collection.worker.CollectorPluginReport; -import eu.dnetlib.dhp.collection.worker.HttpConnector2; -import eu.dnetlib.dhp.collection.worker.XmlCleaner; +import eu.dnetlib.dhp.collection.CollectorException; +import eu.dnetlib.dhp.collection.CollectorPluginReport; +import eu.dnetlib.dhp.collection.HttpConnector2; +import eu.dnetlib.dhp.collection.XmlCleaner; public class OaiIterator implements Iterator { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java index f63fa37a1..d7b5de087 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java @@ -3,9 +3,9 @@ package eu.dnetlib.dhp.collection.plugin.oai; import java.util.Iterator; -import eu.dnetlib.dhp.collection.worker.CollectorPluginReport; -import eu.dnetlib.dhp.collection.worker.HttpClientParams; -import eu.dnetlib.dhp.collection.worker.HttpConnector2; +import eu.dnetlib.dhp.collection.CollectorPluginReport; +import eu.dnetlib.dhp.collection.HttpClientParams; +import eu.dnetlib.dhp.collection.HttpConnector2; public class OaiIteratorFactory { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorPluginFactory.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorPluginFactory.java deleted file mode 100644 index 9668098f0..000000000 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/worker/CollectorPluginFactory.java +++ /dev/null @@ -1,20 +0,0 @@ - -package eu.dnetlib.dhp.collection.worker; - -import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; -import eu.dnetlib.dhp.collection.plugin.oai.OaiCollectorPlugin; - -public class CollectorPluginFactory { - - public static CollectorPlugin getPluginByProtocol(final HttpClientParams clientParams, final String protocol) - throws UnknownCollectorPluginException { - if (protocol == null) - throw new UnknownCollectorPluginException("protocol cannot be null"); - switch (protocol.toLowerCase().trim()) { - case "oai": - return new OaiCollectorPlugin(clientParams); - default: - throw new UnknownCollectorPluginException("Unknown protocol"); - } - } -} diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml index 5497b2c50..1bab59659 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml @@ -98,7 +98,7 @@ - eu.dnetlib.dhp.collection.worker.CollectorWorkerApplication + eu.dnetlib.dhp.collection.CollectorWorkerApplication ${collection_java_xmx} --apidescriptor${apiDescription} --namenode${nameNode} @@ -118,7 +118,7 @@ - eu.dnetlib.dhp.collection.worker.CollectorWorkerReporter + eu.dnetlib.dhp.collection.CollectorWorkerReporter ${collection_java_xmx} --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} --namenode${nameNode} diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java index 7f597f950..acb4caa22 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/project/EXCELParserTest.java @@ -13,8 +13,8 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import eu.dnetlib.dhp.actionmanager.project.utils.EXCELParser; -import eu.dnetlib.dhp.collection.worker.CollectorException; -import eu.dnetlib.dhp.collection.worker.HttpConnector2; +import eu.dnetlib.dhp.collection.CollectorException; +import eu.dnetlib.dhp.collection.HttpConnector2; @Disabled public class EXCELParserTest { diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/CollectionJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/CollectionJobTest.java deleted file mode 100644 index 6f7bb2bc2..000000000 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/CollectionJobTest.java +++ /dev/null @@ -1,130 +0,0 @@ - -package eu.dnetlib.dhp.collection; - -import static org.junit.jupiter.api.Assertions.*; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import org.apache.spark.SparkConf; -import org.apache.spark.sql.SparkSession; -import org.junit.jupiter.api.*; -import org.junit.jupiter.api.io.TempDir; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import eu.dnetlib.data.mdstore.manager.common.model.MDStoreCurrentVersion; -import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; -import eu.dnetlib.dhp.model.mdstore.MetadataRecord; -import eu.dnetlib.dhp.model.mdstore.Provenance; -import eu.dnetlib.dhp.schema.common.ModelSupport; - -public class CollectionJobTest { - - private static SparkSession spark; - - @BeforeAll - public static void beforeAll() { - SparkConf conf = new SparkConf(); - conf.setAppName(CollectionJobTest.class.getSimpleName()); - conf.setMaster("local"); - spark = SparkSession.builder().config(conf).getOrCreate(); - } - - @AfterAll - public static void afterAll() { - spark.stop(); - } - - @Test - public void testJSONSerialization() throws Exception { - final String s = IOUtils.toString(getClass().getResourceAsStream("input.json")); - System.out.println("s = " + s); - final ObjectMapper mapper = new ObjectMapper(); - MDStoreVersion mi = mapper.readValue(s, MDStoreVersion.class); - - assertNotNull(mi); - - } - - @Test - public void tesCollection(@TempDir Path testDir) throws Exception { - final Provenance provenance = new Provenance("pippo", "puppa", "ns_prefix"); - Assertions.assertNotNull(new ObjectMapper().writeValueAsString(provenance)); - - GenerateNativeStoreSparkJob - .main( - new String[] { - "issm", "true", - "-w", "wid", - "-e", "XML", - "-d", "" + System.currentTimeMillis(), - "-p", new ObjectMapper().writeValueAsString(provenance), - "-x", "./*[local-name()='record']/*[local-name()='header']/*[local-name()='identifier']", - "-i", this.getClass().getResource("/eu/dnetlib/dhp/collection/native.seq").toString(), - "-o", testDir.toString() + "/store", - "-t", "true", - "-ru", "", - "-rp", "", - "-rh", "", - "-ro", "", - "-rr", "" - }); - - // TODO introduce useful assertions - - } - - @Test - public void testGenerationMetadataRecord() throws Exception { - - final String xml = IOUtils.toString(this.getClass().getResourceAsStream("./record.xml")); - - final MetadataRecord record = GenerateNativeStoreSparkJob - .parseRecord( - xml, - "./*[local-name()='record']/*[local-name()='header']/*[local-name()='identifier']", - "XML", - new Provenance("foo", "bar", "ns_prefix"), - System.currentTimeMillis(), - null, - null); - - assertNotNull(record.getId()); - assertNotNull(record.getOriginalId()); - } - - @Test - public void TestEquals() throws IOException { - - final String xml = IOUtils.toString(this.getClass().getResourceAsStream("./record.xml")); - final MetadataRecord record = GenerateNativeStoreSparkJob - .parseRecord( - xml, - "./*[local-name()='record']/*[local-name()='header']/*[local-name()='identifier']", - "XML", - new Provenance("foo", "bar", "ns_prefix"), - System.currentTimeMillis(), - null, - null); - final MetadataRecord record1 = GenerateNativeStoreSparkJob - .parseRecord( - xml, - "./*[local-name()='record']/*[local-name()='header']/*[local-name()='identifier']", - "XML", - new Provenance("foo", "bar", "ns_prefix"), - System.currentTimeMillis(), - null, - null); - - record.setBody("ciao"); - record1.setBody("mondo"); - - assertNotNull(record); - assertNotNull(record1); - assertEquals(record, record1); - } -} diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/CollectionWorkflowTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/CollectionWorkflowTest.java new file mode 100644 index 000000000..cd6275d7f --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/CollectionWorkflowTest.java @@ -0,0 +1,113 @@ + +package eu.dnetlib.dhp.collection; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.apache.commons.io.IOUtils; +import org.apache.hadoop.hdfs.DistributedFileSystem; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@ExtendWith(MockitoExtension.class) +public class CollectionWorkflowTest { + + private static final Logger log = LoggerFactory.getLogger(CollectionWorkflowTest.class); + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private static Path workingDir; + + private static DistributedFileSystem fileSystem; + + // private static MiniDFSCluster hdfsCluster; + + private static ApiDescriptor api; + private static String mdStoreVersion; + + private static final String encoding = "XML"; + private static final String dateOfCollection = System.currentTimeMillis() + ""; + private static final String xpath = "//*[local-name()='header']/*[local-name()='identifier']"; + private static String provenance; + + private static final String msgMgrUrl = "http://localhost:%s/mock/mvc/dhp/message"; + + @BeforeAll + protected static void beforeAll() throws Exception { + provenance = IOUtils + .toString(CollectionWorkflowTest.class.getResourceAsStream("/eu/dnetlib/dhp/collection/provenance.json")); + + workingDir = Files.createTempDirectory(CollectionWorkflowTest.class.getSimpleName()); + log.info("using work dir {}", workingDir); + + /* + * Configuration conf = new Configuration(); conf.set(MiniDFSCluster.HDFS_MINIDFS_BASEDIR, + * workingDir.toString()); hdfsCluster = new MiniDFSCluster.Builder(conf).build(); fileSystem = + * hdfsCluster.getFileSystem(); api = OBJECT_MAPPER .readValue( + * IOUtils.toString(CollectionWorkflowTest.class.getResourceAsStream("apiDescriptor.json")), + * ApiDescriptor.class); mdStoreVersion = OBJECT_MAPPER + * .writeValueAsString(prepareVersion("/eu/dnetlib/dhp/collection/mdStoreVersion_1.json")); + */ + } + + @AfterAll + protected static void tearDown() { + /* + * hdfsCluster.shutdown(); FileUtil.fullyDelete(workingDir.toFile()); + */ + + } + + /** + + + eu.dnetlib.dhp.collection.worker.CollectorWorkerApplication + ${collection_java_xmx} + --apidescriptor${apiDescription} + --namenode${nameNode} + --workflowId${workflowId} + --dnetMessageManagerURL${dnetMessageManagerURL} + --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} + --maxNumberOfRetry${maxNumberOfRetry} + --requestDelay${requestDelay} + --retryDelay${retryDelay} + --connectTimeOut${connectTimeOut} + --readTimeOut${readTimeOut} + + + + + + */ + // @Test + // @Order(1) + public void testCollectorWorkerApplication() throws Exception { + + final HttpClientParams httpClientParams = new HttpClientParams(); + + // String url = String.format(msgMgrUrl, wireMockServer.port()); + + // new CollectorWorkerApplication(fileSystem).run(mdStoreVersion, httpClientParams, api, url, "1234"); + + } + + public static MDStoreVersion prepareVersion(String filename) throws IOException { + MDStoreVersion mdstore = OBJECT_MAPPER + .readValue(IOUtils.toString(CollectionWorkflowTest.class.getResource(filename)), MDStoreVersion.class); + mdstore.setHdfsPath(String.format(mdstore.getHdfsPath(), workingDir.toString())); + return mdstore; + } + +} diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJobTest.java similarity index 73% rename from dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java rename to dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJobTest.java index ff3ff3b6e..723f030a6 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/aggregation/AggregationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJobTest.java @@ -1,8 +1,9 @@ -package eu.dnetlib.dhp.aggregation; +package eu.dnetlib.dhp.collection; import static eu.dnetlib.dhp.common.Constants.MDSTORE_DATA_PATH; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.io.File; import java.io.FileOutputStream; @@ -36,14 +37,14 @@ import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; -import eu.dnetlib.dhp.collection.GenerateNativeStoreSparkJob; +import eu.dnetlib.dhp.aggregation.AbstractVocabularyTest; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; +import eu.dnetlib.dhp.model.mdstore.Provenance; import eu.dnetlib.dhp.transformation.TransformSparkJobNode; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @ExtendWith(MockitoExtension.class) -public class AggregationJobTest extends AbstractVocabularyTest { +public class GenerateNativeStoreSparkJobTest extends AbstractVocabularyTest { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @@ -58,18 +59,20 @@ public class AggregationJobTest extends AbstractVocabularyTest { private static final String xpath = "//*[local-name()='header']/*[local-name()='identifier']"; private static String provenance; - private static final Logger log = LoggerFactory.getLogger(AggregationJobTest.class); + private static final Logger log = LoggerFactory.getLogger(GenerateNativeStoreSparkJobTest.class); @BeforeAll public static void beforeAll() throws IOException { provenance = IOUtils - .toString(AggregationJobTest.class.getResourceAsStream("/eu/dnetlib/dhp/collection/provenance.json")); - workingDir = Files.createTempDirectory(AggregationJobTest.class.getSimpleName()); + .toString( + GenerateNativeStoreSparkJobTest.class + .getResourceAsStream("/eu/dnetlib/dhp/collection/provenance.json")); + workingDir = Files.createTempDirectory(GenerateNativeStoreSparkJobTest.class.getSimpleName()); log.info("using work dir {}", workingDir); SparkConf conf = new SparkConf(); - conf.setAppName(AggregationJobTest.class.getSimpleName()); + conf.setAppName(GenerateNativeStoreSparkJobTest.class.getSimpleName()); conf.setMaster("local[*]"); conf.set("spark.driver.host", "localhost"); @@ -81,7 +84,7 @@ public class AggregationJobTest extends AbstractVocabularyTest { encoder = Encoders.bean(MetadataRecord.class); spark = SparkSession .builder() - .appName(AggregationJobTest.class.getSimpleName()) + .appName(GenerateNativeStoreSparkJobTest.class.getSimpleName()) .config(conf) .getOrCreate(); } @@ -202,6 +205,67 @@ public class AggregationJobTest extends AbstractVocabularyTest { } + @Test + public void testJSONSerialization() throws Exception { + final String s = IOUtils.toString(getClass().getResourceAsStream("mdStoreVersion_1.json")); + System.out.println("s = " + s); + final ObjectMapper mapper = new ObjectMapper(); + MDStoreVersion mi = mapper.readValue(s, MDStoreVersion.class); + + assertNotNull(mi); + + } + + @Test + public void testGenerationMetadataRecord() throws Exception { + + final String xml = IOUtils.toString(this.getClass().getResourceAsStream("./record.xml")); + + final MetadataRecord record = GenerateNativeStoreSparkJob + .parseRecord( + xml, + "./*[local-name()='record']/*[local-name()='header']/*[local-name()='identifier']", + "XML", + new Provenance("foo", "bar", "ns_prefix"), + System.currentTimeMillis(), + null, + null); + + assertNotNull(record.getId()); + assertNotNull(record.getOriginalId()); + } + + @Test + public void testEquals() throws IOException { + + final String xml = IOUtils.toString(this.getClass().getResourceAsStream("./record.xml")); + final MetadataRecord record = GenerateNativeStoreSparkJob + .parseRecord( + xml, + "./*[local-name()='record']/*[local-name()='header']/*[local-name()='identifier']", + "XML", + new Provenance("foo", "bar", "ns_prefix"), + System.currentTimeMillis(), + null, + null); + final MetadataRecord record1 = GenerateNativeStoreSparkJob + .parseRecord( + xml, + "./*[local-name()='record']/*[local-name()='header']/*[local-name()='identifier']", + "XML", + new Provenance("foo", "bar", "ns_prefix"), + System.currentTimeMillis(), + null, + null); + + record.setBody("ciao"); + record1.setBody("mondo"); + + assertNotNull(record); + assertNotNull(record1); + assertEquals(record, record1); + } + protected void verify(MDStoreVersion mdStoreVersion) throws IOException { Assertions.assertTrue(new File(mdStoreVersion.getHdfsPath()).exists()); @@ -226,7 +290,7 @@ public class AggregationJobTest extends AbstractVocabularyTest { Assertions.assertEquals(seqFileSize, uniqueIds, "the size must be equal"); } - private MDStoreVersion prepareVersion(String filename) throws IOException { + public MDStoreVersion prepareVersion(String filename) throws IOException { MDStoreVersion mdstore = OBJECT_MAPPER .readValue(IOUtils.toString(getClass().getResource(filename)), MDStoreVersion.class); mdstore.setHdfsPath(String.format(mdstore.getHdfsPath(), workingDir.toString())); diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/CollectorWorkerApplicationTests.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/CollectorWorkerApplicationTests.java index 975ef944e..b5ea5f069 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/CollectorWorkerApplicationTests.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/CollectorWorkerApplicationTests.java @@ -9,20 +9,10 @@ import org.junit.jupiter.api.Test; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.collection.ApiDescriptor; -import eu.dnetlib.dhp.collection.worker.CollectorPluginFactory; -import eu.dnetlib.dhp.collection.worker.HttpClientParams; @Disabled public class CollectorWorkerApplicationTests { - @Test - public void testFindPlugin() throws Exception { - final CollectorPluginFactory collectorPluginEnumerator = new CollectorPluginFactory(); - final HttpClientParams clientParams = new HttpClientParams(); - assertNotNull(collectorPluginEnumerator.getPluginByProtocol(clientParams, "oai")); - assertNotNull(collectorPluginEnumerator.getPluginByProtocol(clientParams, "OAI")); - } - @Test public void testCollectionOAI() throws Exception { final ApiDescriptor api = new ApiDescriptor(); diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/utils/CollectorPluginReportTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/utils/CollectorPluginReportTest.java index d665e5b5f..fd90a1b84 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/utils/CollectorPluginReportTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/utils/CollectorPluginReportTest.java @@ -8,7 +8,7 @@ import java.io.IOException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import eu.dnetlib.dhp.collection.worker.CollectorPluginReport; +import eu.dnetlib.dhp.collection.CollectorPluginReport; public class CollectorPluginReportTest { diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java index 997727e33..356fb252d 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java @@ -27,7 +27,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import eu.dnetlib.dhp.aggregation.AbstractVocabularyTest; import eu.dnetlib.dhp.aggregation.common.AggregationCounter; -import eu.dnetlib.dhp.collection.CollectionJobTest; +import eu.dnetlib.dhp.collection.GenerateNativeStoreSparkJobTest; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; import eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; @@ -40,7 +40,7 @@ public class TransformationJobTest extends AbstractVocabularyTest { @BeforeAll public static void beforeAll() throws IOException, ISLookUpException { SparkConf conf = new SparkConf(); - conf.setAppName(CollectionJobTest.class.getSimpleName()); + conf.setAppName(GenerateNativeStoreSparkJobTest.class.getSimpleName()); conf.setMaster("local"); spark = SparkSession.builder().config(conf).getOrCreate(); } diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/apiDescriptor.json b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/apiDescriptor.json new file mode 100644 index 000000000..99957cac9 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/collection/apiDescriptor.json @@ -0,0 +1,10 @@ +{ + "id":"api_________::opendoar____::2::0", + "baseUrl":"https://www.alexandria.unisg.ch/cgi/oai2", + "protocol":"oai", + "params": { + "set":"driver", + "metadata_identifier_path":"//*[local-name()\u003d\u0027header\u0027]/*[local-name()\u003d\u0027identifier\u0027]", + "format":"oai_dc" + } +} \ No newline at end of file diff --git a/dhp-workflows/dhp-graph-provision/src/test/resources/eu/dnetlib/dhp/oa/provision/fields.xml b/dhp-workflows/dhp-graph-provision/src/test/resources/eu/dnetlib/dhp/oa/provision/fields.xml index 1f5cf7b81..0352092b2 100644 --- a/dhp-workflows/dhp-graph-provision/src/test/resources/eu/dnetlib/dhp/oa/provision/fields.xml +++ b/dhp-workflows/dhp-graph-provision/src/test/resources/eu/dnetlib/dhp/oa/provision/fields.xml @@ -79,6 +79,8 @@ + + diff --git a/dhp-workflows/dhp-graph-provision/src/test/resources/eu/dnetlib/dhp/oa/provision/record.xml b/dhp-workflows/dhp-graph-provision/src/test/resources/eu/dnetlib/dhp/oa/provision/record.xml index b617dbea2..a0ca0aa6f 100644 --- a/dhp-workflows/dhp-graph-provision/src/test/resources/eu/dnetlib/dhp/oa/provision/record.xml +++ b/dhp-workflows/dhp-graph-provision/src/test/resources/eu/dnetlib/dhp/oa/provision/record.xml @@ -39,6 +39,8 @@ Saykally, Jessica N. Keeley, Kristen L. Haris Hatic + Baglioni, Miriam + De Bonis, Michele 2017-06-01 Withania somnifera has been used in traditional medicine for a variety of neural disorders. Recently, chronic neurodegenerative conditions have been @@ -115,7 +117,7 @@ Cell Transplantation - + Cell Transplantation From 5a9017cf18860ddcd3588e62804826b124463333 Mon Sep 17 00:00:00 2001 From: Andreas Czerniak Date: Fri, 12 Feb 2021 14:32:36 +0100 Subject: [PATCH 110/445] clone, min. changes, test, run --- .../main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java | 2 +- .../src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl | 2 +- .../src/test/resources/eu/dnetlib/dhp/transform/tr.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java index 7b0fdd484..1343a99b9 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java @@ -16,7 +16,7 @@ public class Cleaner implements ExtensionFunction, Serializable { @Override public QName getName() { - return new QName("http://eu/dnetlib/trasform/extension", "clean"); + return new QName("http://eu/dnetlib/transform/extension", "clean"); } @Override diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl index becd3a05e..aed3de656 100644 --- a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl @@ -1,7 +1,7 @@ diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/tr.xml b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/tr.xml index a9eae8576..77fccb4d3 100644 --- a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/tr.xml +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/tr.xml @@ -16,7 +16,7 @@ From f216277219a164ddb12b9c99610a368eac0212c1 Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Fri, 12 Feb 2021 16:34:52 +0100 Subject: [PATCH 111/445] Implemented cleaning date --- .../dhp/transformation/xslt/Cleaner.java | 2 +- .../dhp/transformation/xslt/DateCleaner.java | 100 ++++++++++++++++++ .../xslt/XSLTTransformationFunction.java | 1 + .../transformation/TransformationJobTest.java | 30 ++++-- .../eu/dnetlib/dhp/transform/input_zenodo.xml | 2 +- .../eu/dnetlib/dhp/transform/zenodo_tr.xslt | 7 +- 6 files changed, 128 insertions(+), 14 deletions(-) create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/DateCleaner.java diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java index 8b7024cbe..fbf47c2ff 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java @@ -35,7 +35,7 @@ public class Cleaner implements ExtensionFunction, Serializable { @Override public XdmValue call(XdmValue[] xdmValues) throws SaxonApiException { XdmValue r = xdmValues[0]; - if (r.size() == 0){ + if (r.size() == 0) { return new XdmAtomicValue(""); } final String currentValue = xdmValues[0].itemAt(0).getStringValue(); diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/DateCleaner.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/DateCleaner.java new file mode 100644 index 000000000..98bea8de4 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/DateCleaner.java @@ -0,0 +1,100 @@ +package eu.dnetlib.dhp.transformation.xslt; + +import net.sf.saxon.s9api.*; +import scala.Serializable; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class DateCleaner implements ExtensionFunction, Serializable { + + private final static List dateRegex = Arrays.asList( + //Y-M-D + Pattern.compile("(18|19|20)\\d\\d([- /.])(0[1-9]|1[012])\\2(0[1-9]|[12][0-9]|3[01])", Pattern.MULTILINE), + //M-D-Y + Pattern.compile("((0[1-9]|1[012])|([1-9]))([- /.])(0[1-9]|[12][0-9]|3[01])([- /.])(18|19|20)?\\d\\d", Pattern.MULTILINE), + //D-M-Y + Pattern.compile("(?:(?:31(/|-|\\.)(?:0?[13578]|1[02]|(?:Jan|Mar|May|Jul|Aug|Oct|Dec)))\\1|(?:(?:29|30)(/|-|\\.)(?:0?[1,3-9]|1[0-2]|(?:Jan|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec))\\2))(?:(?:1[6-9]|[2-9]\\d)?\\d{2})|(?:29(/|-|\\.)(?:0?2|(?:Feb))\\3(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))|(?:0?[1-9]|1\\d|2[0-8])(/|-|\\.)(?:(?:0?[1-9]|(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep))|(?:1[0-2]|(?:Oct|Nov|Dec)))\\4(?:(?:1[6-9]|[2-9]\\d)?\\d{2})", Pattern.MULTILINE), + //Y + Pattern.compile("(19|20)\\d\\d", Pattern.MULTILINE) + ); + + private final static Pattern incompleteDateRegex = Pattern.compile("^((18|19|20)\\d\\d){1}([- \\\\ \\/](0?[1-9]|1[012]))?", Pattern.MULTILINE); + + private final static List dformats = Arrays.asList( + DateTimeFormatter.ofPattern("[MM-dd-yyyy][MM/dd/yyyy][dd-MM-yy][dd-MMM-yyyy][dd/MMM/yyyy][dd-MMM-yy][dd/MMM/yy][dd-MM-yy][dd/MM/yy][dd-MM-yyyy][dd/MM/yyyy][yyyy-MM-dd][yyyy/MM/dd]", Locale.ENGLISH), + DateTimeFormatter.ofPattern("[dd-MM-yyyy][dd/MM/yyyy]", Locale.ITALIAN) + ); + + public String clean(final String inputDate) { + + Optional cleanedDate = dateRegex.stream().map( + p -> { + final Matcher matcher = p.matcher(inputDate); + if (matcher.find()) + return matcher.group(0); + else + return null; + } + ).filter(Objects::nonNull) + .map(m -> { + Optional cleanDate = dformats.stream() + .map(f -> { + try { + LocalDate parsedDate = LocalDate.parse(m, f); + if (parsedDate != null) + return parsedDate.toString(); + else + return null; + } catch (Throwable e) { + return null; + } + } + + ).filter(Objects::nonNull).findAny(); + + return cleanDate.orElse(null); + }).filter(Objects::nonNull).findAny(); + + if (cleanedDate.isPresent()) + return cleanedDate.get(); + + final Matcher matcher = incompleteDateRegex.matcher(inputDate); + if (matcher.find()){ + final Integer year = Integer.parseInt(matcher.group(1)); + final Integer month = Integer.parseInt(matcher.group(4) == null ? "01":matcher.group(4)); + return String.format("%d-%02d-01",year, month); + } + return null; + } + + @Override + public QName getName() { + return new QName("http://eu/dnetlib/trasform/dates", "dateISO"); + } + + @Override + public SequenceType getResultType() { + return SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ZERO_OR_ONE); + } + + @Override + public SequenceType[] getArgumentTypes() { + return new SequenceType[] { + SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ZERO_OR_ONE) + }; + } + + @Override + public XdmValue call(XdmValue[] xdmValues) throws SaxonApiException { + XdmValue r = xdmValues[0]; + if (r.size() == 0) { + return new XdmAtomicValue(""); + } + final String currentValue = xdmValues[0].itemAt(0).getStringValue(); + return new XdmAtomicValue(clean(currentValue)); + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java index d8707cd76..d37832bb4 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java @@ -41,6 +41,7 @@ public class XSLTTransformationFunction implements MapFunctionADCP - 2019-05-29 + 2019 diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/zenodo_tr.xslt b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/zenodo_tr.xslt index e67ed9dda..23e57579b 100644 --- a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/zenodo_tr.xslt +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/zenodo_tr.xslt @@ -4,8 +4,9 @@ xmlns:oai="http://www.openarchives.org/OAI/2.0/" xmlns:oaf="http://namespace.openaire.eu/oaf" xmlns:vocabulary="http://eu/dnetlib/trasform/extension" + xmlns:dateCleaner="http://eu/dnetlib/trasform/dates" xmlns:dr="http://www.driver-repository.eu/namespace/dr" - exclude-result-prefixes="xsl vocabulary"> + exclude-result-prefixes="xsl vocabulary dateCleaner"> @@ -53,7 +54,7 @@ + select="dateCleaner:dateISO(normalize-space(//*[local-name()='date'][@dateType='Available']))"/> @@ -112,7 +113,7 @@ + select="dateCleaner:dateISO(normalize-space(//*[local-name()='publicationYear']))"/> From 6a37c7f175eb7efc7e2d16fb6f10830851ae1b57 Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Fri, 12 Feb 2021 16:38:47 +0100 Subject: [PATCH 112/445] merge fixed --- .../eu/dnetlib/dhp/transformation/TransformationJobTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java index df71a513b..b76f9bce6 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java @@ -28,7 +28,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import eu.dnetlib.dhp.aggregation.AbstractVocabularyTest; import eu.dnetlib.dhp.aggregation.common.AggregationCounter; -import eu.dnetlib.dhp.collection.CollectionJobTest; + import eu.dnetlib.dhp.model.mdstore.MetadataRecord; import eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; @@ -41,7 +41,7 @@ public class TransformationJobTest extends AbstractVocabularyTest { @BeforeAll public static void beforeAll() throws IOException, ISLookUpException { SparkConf conf = new SparkConf(); - conf.setAppName(CollectionJobTest.class.getSimpleName()); + conf.setAppName(TransformationJobTest.class.getSimpleName()); conf.setMaster("local"); spark = SparkSession.builder().config(conf).getOrCreate(); } From 7edcc87ed4689b41593a0ac5382fc62f59ac7fe7 Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Fri, 12 Feb 2021 17:27:08 +0100 Subject: [PATCH 113/445] changed xslt behaviour on failure --- .../transformation/TransformSparkJobNode.java | 3 +- .../dhp/transformation/xslt/DateCleaner.java | 170 ++++++++++-------- .../xslt/XSLTTransformationFunction.java | 2 +- .../transformation/TransformationJobTest.java | 19 +- 4 files changed, 103 insertions(+), 91 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java index e628e7645..f9a18987d 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java @@ -106,7 +106,8 @@ public class TransformSparkJobNode { log.info("Transformation Error item " + ct.getErrorItems().count()); writeHdfsFile( - spark.sparkContext().hadoopConfiguration(), "" + mdstore.count(), outputBasePath + MDSTORE_SIZE_PATH); + spark.sparkContext().hadoopConfiguration(), + "" + spark.read().load(outputBasePath + MDSTORE_DATA_PATH).count(), outputBasePath + MDSTORE_SIZE_PATH); } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/DateCleaner.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/DateCleaner.java index 98bea8de4..4e1a29b52 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/DateCleaner.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/DateCleaner.java @@ -1,100 +1,118 @@ + package eu.dnetlib.dhp.transformation.xslt; -import net.sf.saxon.s9api.*; -import scala.Serializable; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; +import net.sf.saxon.s9api.*; +import scala.Serializable; public class DateCleaner implements ExtensionFunction, Serializable { - private final static List dateRegex = Arrays.asList( - //Y-M-D - Pattern.compile("(18|19|20)\\d\\d([- /.])(0[1-9]|1[012])\\2(0[1-9]|[12][0-9]|3[01])", Pattern.MULTILINE), - //M-D-Y - Pattern.compile("((0[1-9]|1[012])|([1-9]))([- /.])(0[1-9]|[12][0-9]|3[01])([- /.])(18|19|20)?\\d\\d", Pattern.MULTILINE), - //D-M-Y - Pattern.compile("(?:(?:31(/|-|\\.)(?:0?[13578]|1[02]|(?:Jan|Mar|May|Jul|Aug|Oct|Dec)))\\1|(?:(?:29|30)(/|-|\\.)(?:0?[1,3-9]|1[0-2]|(?:Jan|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec))\\2))(?:(?:1[6-9]|[2-9]\\d)?\\d{2})|(?:29(/|-|\\.)(?:0?2|(?:Feb))\\3(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))|(?:0?[1-9]|1\\d|2[0-8])(/|-|\\.)(?:(?:0?[1-9]|(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep))|(?:1[0-2]|(?:Oct|Nov|Dec)))\\4(?:(?:1[6-9]|[2-9]\\d)?\\d{2})", Pattern.MULTILINE), - //Y - Pattern.compile("(19|20)\\d\\d", Pattern.MULTILINE) - ); + private final static List dateRegex = Arrays + .asList( + // Y-M-D + Pattern.compile("(18|19|20)\\d\\d([- /.])(0[1-9]|1[012])\\2(0[1-9]|[12][0-9]|3[01])", Pattern.MULTILINE), + // M-D-Y + Pattern + .compile( + "((0[1-9]|1[012])|([1-9]))([- /.])(0[1-9]|[12][0-9]|3[01])([- /.])(18|19|20)?\\d\\d", + Pattern.MULTILINE), + // D-M-Y + Pattern + .compile( + "(?:(?:31(/|-|\\.)(?:0?[13578]|1[02]|(?:Jan|Mar|May|Jul|Aug|Oct|Dec)))\\1|(?:(?:29|30)(/|-|\\.)(?:0?[1,3-9]|1[0-2]|(?:Jan|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec))\\2))(?:(?:1[6-9]|[2-9]\\d)?\\d{2})|(?:29(/|-|\\.)(?:0?2|(?:Feb))\\3(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))|(?:0?[1-9]|1\\d|2[0-8])(/|-|\\.)(?:(?:0?[1-9]|(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep))|(?:1[0-2]|(?:Oct|Nov|Dec)))\\4(?:(?:1[6-9]|[2-9]\\d)?\\d{2})", + Pattern.MULTILINE), + // Y + Pattern.compile("(19|20)\\d\\d", Pattern.MULTILINE)); - private final static Pattern incompleteDateRegex = Pattern.compile("^((18|19|20)\\d\\d){1}([- \\\\ \\/](0?[1-9]|1[012]))?", Pattern.MULTILINE); + private final static Pattern incompleteDateRegex = Pattern + .compile("^((18|19|20)\\d\\d){1}([- \\\\ \\/](0?[1-9]|1[012]))?", Pattern.MULTILINE); - private final static List dformats = Arrays.asList( - DateTimeFormatter.ofPattern("[MM-dd-yyyy][MM/dd/yyyy][dd-MM-yy][dd-MMM-yyyy][dd/MMM/yyyy][dd-MMM-yy][dd/MMM/yy][dd-MM-yy][dd/MM/yy][dd-MM-yyyy][dd/MM/yyyy][yyyy-MM-dd][yyyy/MM/dd]", Locale.ENGLISH), - DateTimeFormatter.ofPattern("[dd-MM-yyyy][dd/MM/yyyy]", Locale.ITALIAN) - ); + private final static List dformats = Arrays + .asList( + DateTimeFormatter + .ofPattern( + "[MM-dd-yyyy][MM/dd/yyyy][dd-MM-yy][dd-MMM-yyyy][dd/MMM/yyyy][dd-MMM-yy][dd/MMM/yy][dd-MM-yy][dd/MM/yy][dd-MM-yyyy][dd/MM/yyyy][yyyy-MM-dd][yyyy/MM/dd]", + Locale.ENGLISH), + DateTimeFormatter.ofPattern("[dd-MM-yyyy][dd/MM/yyyy]", Locale.ITALIAN)); - public String clean(final String inputDate) { + public String clean(final String inputDate) { - Optional cleanedDate = dateRegex.stream().map( - p -> { - final Matcher matcher = p.matcher(inputDate); - if (matcher.find()) - return matcher.group(0); - else - return null; - } - ).filter(Objects::nonNull) - .map(m -> { - Optional cleanDate = dformats.stream() - .map(f -> { - try { - LocalDate parsedDate = LocalDate.parse(m, f); - if (parsedDate != null) - return parsedDate.toString(); - else - return null; - } catch (Throwable e) { - return null; - } - } + Optional cleanedDate = dateRegex + .stream() + .map( + p -> { + final Matcher matcher = p.matcher(inputDate); + if (matcher.find()) + return matcher.group(0); + else + return null; + }) + .filter(Objects::nonNull) + .map(m -> { + Optional cleanDate = dformats + .stream() + .map(f -> { + try { + LocalDate parsedDate = LocalDate.parse(m, f); + if (parsedDate != null) + return parsedDate.toString(); + else + return null; + } catch (Throwable e) { + return null; + } + } - ).filter(Objects::nonNull).findAny(); + ) + .filter(Objects::nonNull) + .findAny(); - return cleanDate.orElse(null); - }).filter(Objects::nonNull).findAny(); + return cleanDate.orElse(null); + }) + .filter(Objects::nonNull) + .findAny(); - if (cleanedDate.isPresent()) - return cleanedDate.get(); + if (cleanedDate.isPresent()) + return cleanedDate.get(); - final Matcher matcher = incompleteDateRegex.matcher(inputDate); - if (matcher.find()){ - final Integer year = Integer.parseInt(matcher.group(1)); - final Integer month = Integer.parseInt(matcher.group(4) == null ? "01":matcher.group(4)); - return String.format("%d-%02d-01",year, month); - } - return null; - } + final Matcher matcher = incompleteDateRegex.matcher(inputDate); + if (matcher.find()) { + final Integer year = Integer.parseInt(matcher.group(1)); + final Integer month = Integer.parseInt(matcher.group(4) == null ? "01" : matcher.group(4)); + return String.format("%d-%02d-01", year, month); + } + return null; + } - @Override - public QName getName() { - return new QName("http://eu/dnetlib/trasform/dates", "dateISO"); - } + @Override + public QName getName() { + return new QName("http://eu/dnetlib/trasform/dates", "dateISO"); + } - @Override - public SequenceType getResultType() { - return SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ZERO_OR_ONE); - } + @Override + public SequenceType getResultType() { + return SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ZERO_OR_ONE); + } - @Override - public SequenceType[] getArgumentTypes() { - return new SequenceType[] { - SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ZERO_OR_ONE) - }; - } + @Override + public SequenceType[] getArgumentTypes() { + return new SequenceType[] { + SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ZERO_OR_ONE) + }; + } - @Override - public XdmValue call(XdmValue[] xdmValues) throws SaxonApiException { - XdmValue r = xdmValues[0]; - if (r.size() == 0) { - return new XdmAtomicValue(""); - } - final String currentValue = xdmValues[0].itemAt(0).getStringValue(); - return new XdmAtomicValue(clean(currentValue)); - } + @Override + public XdmValue call(XdmValue[] xdmValues) throws SaxonApiException { + XdmValue r = xdmValues[0]; + if (r.size() == 0) { + return new XdmAtomicValue(""); + } + final String currentValue = xdmValues[0].itemAt(0).getStringValue(); + return new XdmAtomicValue(clean(currentValue)); + } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java index d37832bb4..7d47cc84d 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java @@ -63,7 +63,7 @@ public class XSLTTransformationFunction implements MapFunction Date: Mon, 15 Feb 2021 15:08:59 +0100 Subject: [PATCH 114/445] WIP: collectorWorker error reporting, added report messages --- .../java/eu/dnetlib/dhp/message/Message.java | 22 ++++--- .../eu/dnetlib/dhp/message/MessageSender.java | 11 ++-- .../eu/dnetlib/dhp/message/MessageType.java | 20 ++++++ .../dhp/collection/CollectorPluginReport.java | 38 +++--------- .../dhp/collection/CollectorWorker.java | 10 +-- .../CollectorWorkerApplication.java | 15 ++--- .../collection/CollectorWorkerReporter.java | 62 ------------------- .../collector_reporter_input_parameter.json | 14 ----- .../dhp/collection/oozie_app/workflow.xml | 12 ---- .../utils/CollectorPluginReportTest.java | 30 --------- 10 files changed, 57 insertions(+), 177 deletions(-) create mode 100644 dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageType.java delete mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorkerReporter.java delete mode 100644 dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_reporter_input_parameter.json delete mode 100644 dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/utils/CollectorPluginReportTest.java diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java b/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java index ed2a3c9b3..0cbb6c859 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java @@ -3,31 +3,36 @@ package eu.dnetlib.dhp.message; import java.io.Serializable; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; public class Message implements Serializable { + private static final long serialVersionUID = 401753881204524893L; + public static String CURRENT_PARAM = "current"; public static String TOTAL_PARAM = "total"; - /** - * - */ - private static final long serialVersionUID = 401753881204524893L; + private MessageType messageType; private String workflowId; private Map body; - public Message() { - body = new HashMap<>(); + public Message(final MessageType messageType, final String workflowId) { + this(messageType, workflowId, new LinkedHashMap<>()); } - public Message(final String workflowId, final Map body) { + public Message(final MessageType messageType, final String workflowId, final Map body) { + this.messageType = messageType; this.workflowId = workflowId; this.body = body; } + public MessageType getMessageType() { + return messageType; + } + public String getWorkflowId() { return workflowId; } @@ -46,6 +51,7 @@ public class Message implements Serializable { @Override public String toString() { - return String.format("Message [workflowId=%s, body=%s]", workflowId, body); + return String.format("Message [type=%s, workflowId=%s, body=%s]", messageType, workflowId, body); } + } diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageSender.java b/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageSender.java index 16bb0c97e..0c6eacf99 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageSender.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageSender.java @@ -1,6 +1,7 @@ package eu.dnetlib.dhp.message; +import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -45,13 +46,15 @@ public class MessageSender { } public void sendMessage(final Long current, final Long total) { - sendMessage(createMessage(current, total)); + sendMessage(createOngoingMessage(current, total)); } - private Message createMessage(final Long current, final Long total) { + public void sendReport(final Map report) { + sendMessage(new Message(MessageType.REPORT, workflowId, report)); + } - final Message m = new Message(); - m.setWorkflowId(workflowId); + private Message createOngoingMessage(final Long current, final Long total) { + final Message m = new Message(MessageType.ONGOING, workflowId); m.getBody().put(Message.CURRENT_PARAM, current.toString()); if (total != null) { m.getBody().put(Message.TOTAL_PARAM, total.toString()); diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageType.java b/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageType.java new file mode 100644 index 000000000..30f152c96 --- /dev/null +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageType.java @@ -0,0 +1,20 @@ + +package eu.dnetlib.dhp.message; + +import java.util.Optional; + +import org.apache.commons.lang3.StringUtils; + +public enum MessageType { + + ONGOING, REPORT; + + public MessageType from(String value) { + return Optional + .ofNullable(value) + .map(StringUtils::upperCase) + .map(MessageType::valueOf) + .orElseThrow(() -> new IllegalArgumentException("unknown message type: " + value)); + } + +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorPluginReport.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorPluginReport.java index a7204523a..a10572d06 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorPluginReport.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorPluginReport.java @@ -1,57 +1,39 @@ package eu.dnetlib.dhp.collection; -import static eu.dnetlib.dhp.utils.DHPUtils.*; - import java.io.Closeable; import java.io.IOException; import java.util.LinkedHashMap; import java.util.Objects; -import org.apache.commons.io.IOUtils; -import org.apache.hadoop.fs.FSDataOutputStream; -import org.apache.hadoop.fs.FileSystem; -import org.apache.hadoop.fs.Path; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.annotation.JsonIgnore; +import eu.dnetlib.dhp.message.MessageSender; public class CollectorPluginReport extends LinkedHashMap implements Closeable { private static final Logger log = LoggerFactory.getLogger(CollectorPluginReport.class); - @JsonIgnore - private Path path; - - @JsonIgnore - private FSDataOutputStream fos; - - public static String SUCCESS = "success"; + private MessageSender messageSender; public CollectorPluginReport() { } - public CollectorPluginReport(FileSystem fs, Path path) throws IOException { - this.path = path; - this.fos = fs.create(path); + public CollectorPluginReport(MessageSender messageSender) throws IOException { + this.messageSender = messageSender; } - public Boolean isSuccess() { - return containsKey(SUCCESS) && Boolean.valueOf(get(SUCCESS)); - } - - public void setSuccess(Boolean success) { - put(SUCCESS, String.valueOf(success)); + public void ongoing(Long current, Long total) { + messageSender.sendMessage(current, total); } @Override public void close() throws IOException { - final String data = MAPPER.writeValueAsString(this); - if (Objects.nonNull(fos)) { - log.info("writing report {} to {}", data, path.toString()); - IOUtils.write(data, fos); - populateOOZIEEnv(this); + if (Objects.nonNull(messageSender)) { + log.info("closing report: "); + this.forEach((k, v) -> log.info("{} - {}", k, v)); + messageSender.sendReport(this); } } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java index ace725bfd..04e0f70c4 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java @@ -38,20 +38,16 @@ public class CollectorWorker { private final CollectorPluginReport report; - private final MessageSender messageSender; - public CollectorWorker( final ApiDescriptor api, final FileSystem fileSystem, final MDStoreVersion mdStoreVersion, final HttpClientParams clientParams, - final MessageSender messageSender, final CollectorPluginReport report) { this.api = api; this.fileSystem = fileSystem; this.mdStoreVersion = mdStoreVersion; this.clientParams = clientParams; - this.messageSender = messageSender; this.report = report; } @@ -78,23 +74,21 @@ public class CollectorWorker { content -> { key.set(counter.getAndIncrement()); if (counter.get() % 500 == 0) - messageSender.sendMessage(counter.longValue(), null); + report.ongoing(counter.longValue(), null); value.set(content); try { writer.append(key, value); } catch (Throwable e) { report.put(e.getClass().getName(), e.getMessage()); log.warn("setting report to failed"); - report.setSuccess(false); throw new RuntimeException(e); } }); } catch (Throwable e) { report.put(e.getClass().getName(), e.getMessage()); log.warn("setting report to failed"); - report.setSuccess(false); } finally { - messageSender.sendMessage(counter.longValue(), counter.longValue()); + report.ongoing(counter.longValue(), counter.longValue()); } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorkerApplication.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorkerApplication.java index 0eea0837c..15f3f20b5 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorkerApplication.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorkerApplication.java @@ -77,22 +77,15 @@ public class CollectorWorkerApplication { } protected void run(String mdStoreVersion, HttpClientParams clientParams, ApiDescriptor api, - String dnetMessageManagerURL, String workflowId) throws IOException { + String dnetMessageManagerURL, String workflowId) + throws IOException, CollectorException, UnknownCollectorPluginException { final MessageSender ms = new MessageSender(dnetMessageManagerURL, workflowId); final MDStoreVersion currentVersion = MAPPER.readValue(mdStoreVersion, MDStoreVersion.class); - final String reportPath = currentVersion.getHdfsPath() + REPORT_FILE_NAME; - log.info("report path is {}", reportPath); - - try (CollectorPluginReport report = new CollectorPluginReport(fileSystem, new Path(reportPath))) { - final CollectorWorker worker = new CollectorWorker(api, fileSystem, currentVersion, clientParams, ms, - report); - worker.collect(); - report.setSuccess(true); - } catch (Throwable e) { - log.info("got exception {}, ignoring", e.getMessage()); + try (CollectorPluginReport report = new CollectorPluginReport(ms)) { + new CollectorWorker(api, fileSystem, currentVersion, clientParams, report).collect(); } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorkerReporter.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorkerReporter.java deleted file mode 100644 index d8cf3ec02..000000000 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorkerReporter.java +++ /dev/null @@ -1,62 +0,0 @@ - -package eu.dnetlib.dhp.collection; - -import static eu.dnetlib.dhp.common.Constants.REPORT_FILE_NAME; -import static eu.dnetlib.dhp.utils.DHPUtils.*; - -import java.io.IOException; -import java.util.Objects; - -import org.apache.commons.cli.ParseException; -import org.apache.commons.io.IOUtils; -import org.apache.hadoop.conf.Configuration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; -import eu.dnetlib.dhp.application.ArgumentApplicationParser; - -/** - * CollectorWorkerReporter - */ -public class CollectorWorkerReporter { - - private static final Logger log = LoggerFactory.getLogger(CollectorWorkerReporter.class); - - /** - * @param args - */ - public static void main(final String[] args) throws IOException, ParseException, CollectorException { - - final ArgumentApplicationParser argumentParser = new ArgumentApplicationParser( - IOUtils - .toString( - CollectorWorker.class - .getResourceAsStream( - "/eu/dnetlib/dhp/collection/collector_reporter_input_parameter.json"))); - argumentParser.parseArgument(args); - - final String nameNode = argumentParser.get("namenode"); - log.info("nameNode is {}", nameNode); - - final String mdStoreVersion = argumentParser.get("mdStoreVersion"); - log.info("mdStoreVersion is {}", mdStoreVersion); - - final MDStoreVersion currentVersion = MAPPER.readValue(mdStoreVersion, MDStoreVersion.class); - - final String reportPath = currentVersion.getHdfsPath() + REPORT_FILE_NAME; - log.info("report path is {}", reportPath); - - final Configuration conf = getHadoopConfiguration(nameNode); - CollectorPluginReport report = readHdfsFileAs(conf, reportPath, CollectorPluginReport.class); - if (Objects.isNull(report)) { - throw new CollectorException("collection report is NULL"); - } - log.info("report success: {}, size: {}", report.isSuccess(), report.size()); - report.forEach((k, v) -> log.info("{} - {}", k, v)); - if (!report.isSuccess()) { - throw new CollectorException("collection report indicates a failure"); - } - } - -} diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_reporter_input_parameter.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_reporter_input_parameter.json deleted file mode 100644 index ef65cc389..000000000 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/collector_reporter_input_parameter.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "paramName": "n", - "paramLongName": "namenode", - "paramDescription": "the Name Node URI", - "paramRequired": true - }, - { - "paramName": "mv", - "paramLongName": "mdStoreVersion", - "paramDescription": "the MDStore Version bean", - "paramRequired": true - } -] diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml index 1bab59659..0678eed11 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/collection/oozie_app/workflow.xml @@ -110,18 +110,6 @@ --retryDelay${retryDelay} --connectTimeOut${connectTimeOut} --readTimeOut${readTimeOut} - - - - - - - - - eu.dnetlib.dhp.collection.CollectorWorkerReporter - ${collection_java_xmx} - --mdStoreVersion${wf:actionData('StartTransaction')['mdStoreVersion']} - --namenode${nameNode} diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/utils/CollectorPluginReportTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/utils/CollectorPluginReportTest.java deleted file mode 100644 index fd90a1b84..000000000 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collector/worker/utils/CollectorPluginReportTest.java +++ /dev/null @@ -1,30 +0,0 @@ - -package eu.dnetlib.dhp.collector.worker.utils; - -import static eu.dnetlib.dhp.utils.DHPUtils.*; - -import java.io.IOException; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import eu.dnetlib.dhp.collection.CollectorPluginReport; - -public class CollectorPluginReportTest { - - @Test - public void testSerialize() throws IOException { - CollectorPluginReport r1 = new CollectorPluginReport(); - r1.put("a", "b"); - r1.setSuccess(true); - - String s = MAPPER.writeValueAsString(r1); - - Assertions.assertNotNull(s); - - CollectorPluginReport r2 = MAPPER.readValue(s, CollectorPluginReport.class); - - Assertions.assertTrue(r2.isSuccess(), "should be true"); - } - -} From 58288a95b8f3eec2a09c677a33c7a9df74c4f250 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 15 Feb 2021 15:28:53 +0100 Subject: [PATCH 115/445] WIP: collectorWorker error reporting, added report messages --- .../src/main/java/eu/dnetlib/dhp/message/Message.java | 6 ++++++ .../src/main/java/eu/dnetlib/dhp/message/MessageType.java | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java b/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java index 0cbb6c859..ecccf8a43 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java @@ -19,6 +19,8 @@ public class Message implements Serializable { private Map body; + public Message() {} + public Message(final MessageType messageType, final String workflowId) { this(messageType, workflowId, new LinkedHashMap<>()); } @@ -33,6 +35,10 @@ public class Message implements Serializable { return messageType; } + public void setMessageType(MessageType messageType) { + this.messageType = messageType; + } + public String getWorkflowId() { return workflowId; } diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageType.java b/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageType.java index 30f152c96..75ffb8ef5 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageType.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/message/MessageType.java @@ -1,11 +1,12 @@ package eu.dnetlib.dhp.message; +import java.io.Serializable; import java.util.Optional; import org.apache.commons.lang3.StringUtils; -public enum MessageType { +public enum MessageType implements Serializable { ONGOING, REPORT; From cf27905a71070c5ea406744d498844714de495f7 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 16 Feb 2021 16:53:14 +0100 Subject: [PATCH 116/445] WIP: collectorWorker error reporting, added report messages --- .../java/eu/dnetlib/dhp/message/Message.java | 3 ++- .../dhp/collection/CollectorPluginReport.java | 10 +++++++- .../dhp/collection/CollectorWorker.java | 19 ++++++++++----- .../collection/plugin/oai/OaiIterator.java | 24 +++++++++---------- .../plugin/oai/OaiIteratorFactory.java | 5 ++-- 5 files changed, 38 insertions(+), 23 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java b/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java index ecccf8a43..f1107b4b8 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/message/Message.java @@ -19,7 +19,8 @@ public class Message implements Serializable { private Map body; - public Message() {} + public Message() { + } public Message(final MessageType messageType, final String workflowId) { this(messageType, workflowId, new LinkedHashMap<>()); diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorPluginReport.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorPluginReport.java index a10572d06..d8f167d49 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorPluginReport.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorPluginReport.java @@ -3,12 +3,17 @@ package eu.dnetlib.dhp.collection; import java.io.Closeable; import java.io.IOException; +import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.Map; import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.base.Joiner; +import com.google.gson.Gson; + import eu.dnetlib.dhp.message.MessageSender; public class CollectorPluginReport extends LinkedHashMap implements Closeable { @@ -33,7 +38,10 @@ public class CollectorPluginReport extends LinkedHashMap impleme if (Objects.nonNull(messageSender)) { log.info("closing report: "); this.forEach((k, v) -> log.info("{} - {}", k, v)); - messageSender.sendReport(this); + + Map m = new HashMap<>(); + m.put(getClass().getSimpleName().toLowerCase(), new Gson().toJson(values())); + messageSender.sendReport(m); } } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java index 04e0f70c4..154b50414 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java @@ -5,6 +5,8 @@ import static eu.dnetlib.dhp.common.Constants.SEQUENCE_FILE_NAME; import java.io.IOException; import java.util.Optional; +import java.util.Timer; +import java.util.TimerTask; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.lang3.StringUtils; @@ -22,11 +24,11 @@ import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; import eu.dnetlib.dhp.collection.plugin.mongodb.MongoDbCollectorPlugin; import eu.dnetlib.dhp.collection.plugin.mongodb.MongoDbDumpCollectorPlugin; import eu.dnetlib.dhp.collection.plugin.oai.OaiCollectorPlugin; -import eu.dnetlib.dhp.message.MessageSender; public class CollectorWorker { private static final Logger log = LoggerFactory.getLogger(CollectorWorker.class); + public static final int ONGOING_REPORT_FREQUENCY_MS = 5000; private final ApiDescriptor api; @@ -59,6 +61,14 @@ public class CollectorWorker { final CollectorPlugin plugin = getCollectorPlugin(); final AtomicInteger counter = new AtomicInteger(0); + final Timer timer = new Timer(); + timer.schedule(new TimerTask() { + @Override + public void run() { + report.ongoing(counter.longValue(), null); + } + }, 5000, ONGOING_REPORT_FREQUENCY_MS); + try (SequenceFile.Writer writer = SequenceFile .createWriter( fileSystem.getConf(), @@ -73,21 +83,18 @@ public class CollectorWorker { .forEach( content -> { key.set(counter.getAndIncrement()); - if (counter.get() % 500 == 0) - report.ongoing(counter.longValue(), null); value.set(content); try { writer.append(key, value); } catch (Throwable e) { - report.put(e.getClass().getName(), e.getMessage()); - log.warn("setting report to failed"); throw new RuntimeException(e); } }); } catch (Throwable e) { report.put(e.getClass().getName(), e.getMessage()); - log.warn("setting report to failed"); + throw new CollectorException(e); } finally { + timer.cancel(); report.ongoing(counter.longValue(), counter.longValue()); } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java index edfcb7bb5..0a0a4c734 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java @@ -38,7 +38,7 @@ public class OaiIterator implements Iterator { private String token; private boolean started; private final HttpConnector2 httpConnector; - private CollectorPluginReport errorLogList; + private CollectorPluginReport report; public OaiIterator( final String baseUrl, @@ -47,7 +47,7 @@ public class OaiIterator implements Iterator { final String fromDate, final String untilDate, final HttpConnector2 httpConnector, - final CollectorPluginReport errorLogList) { + final CollectorPluginReport report) { this.baseUrl = baseUrl; this.mdFormat = mdFormat; this.set = set; @@ -55,7 +55,7 @@ public class OaiIterator implements Iterator { this.untilDate = untilDate; this.started = false; this.httpConnector = httpConnector; - this.errorLogList = errorLogList; + this.report = report; } private void verifyStarted() { @@ -113,7 +113,7 @@ public class OaiIterator implements Iterator { return downloadPage(url); } catch (final UnsupportedEncodingException e) { - errorLogList.put(e.getClass().getName(), e.getMessage()); + report.put(e.getClass().getName(), e.getMessage()); throw new CollectorException(e); } } @@ -139,27 +139,27 @@ public class OaiIterator implements Iterator { + "?verb=ListRecords&resumptionToken=" + URLEncoder.encode(resumptionToken, "UTF-8")); } catch (final UnsupportedEncodingException e) { - errorLogList.put(e.getClass().getName(), e.getMessage()); + report.put(e.getClass().getName(), e.getMessage()); throw new CollectorException(e); } } private String downloadPage(final String url) throws CollectorException { - final String xml = httpConnector.getInputSource(url, errorLogList); + final String xml = httpConnector.getInputSource(url, report); Document doc; try { doc = reader.read(new StringReader(xml)); } catch (final DocumentException e) { log.warn("Error parsing xml, I try to clean it. {}", e.getMessage()); - errorLogList.put(e.getClass().getName(), e.getMessage()); + report.put(e.getClass().getName(), e.getMessage()); final String cleaned = XmlCleaner.cleanAllEntities(xml); try { doc = reader.read(new StringReader(cleaned)); } catch (final DocumentException e1) { final String resumptionToken = extractResumptionToken(xml); if (resumptionToken == null) { - errorLogList.put(e1.getClass().getName(), e1.getMessage()); + report.put(e1.getClass().getName(), e1.getMessage()); throw new CollectorException("Error parsing cleaned document:\n" + cleaned, e1); } return resumptionToken; @@ -172,11 +172,11 @@ public class OaiIterator implements Iterator { if ("noRecordsMatch".equalsIgnoreCase(code)) { final String msg = "noRecordsMatch for oai call : " + url; log.warn(msg); - errorLogList.put(REPORT_PREFIX + code, msg); + report.put(REPORT_PREFIX + code, msg); return null; } else { final String msg = code + " - " + errorNode.getText(); - errorLogList.put(REPORT_PREFIX + "error", msg); + report.put(REPORT_PREFIX + "error", msg); throw new CollectorException(msg); } } @@ -188,7 +188,7 @@ public class OaiIterator implements Iterator { return doc.valueOf("//*[local-name()='resumptionToken']"); } - public CollectorPluginReport getErrorLogList() { - return errorLogList; + public CollectorPluginReport getReport() { + return report; } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java index d7b5de087..a72a62f13 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java @@ -18,9 +18,8 @@ public class OaiIteratorFactory { final String fromDate, final String untilDate, final HttpClientParams clientParams, - final CollectorPluginReport errorLogList) { - return new OaiIterator(baseUrl, mdFormat, set, fromDate, untilDate, getHttpConnector(clientParams), - errorLogList); + final CollectorPluginReport report) { + return new OaiIterator(baseUrl, mdFormat, set, fromDate, untilDate, getHttpConnector(clientParams), report); } private HttpConnector2 getHttpConnector(HttpClientParams clientParams) { From b592d78bb4dafdf2a2f3eac99ef2f511bf41863c Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 17 Feb 2021 10:28:01 +0100 Subject: [PATCH 117/445] WIP: collectorWorker error reporting, generalised reported implementation --- .../common/AggregatorReport.java} | 11 ++-- .../aggregation/common/ReporterCallback.java | 10 ++++ .../dhp/aggregation/common/ReportingJob.java | 41 +++++++++++++ .../dhp/collection/CollectorWorker.java | 59 +++++++++++-------- .../CollectorWorkerApplication.java | 7 +-- .../dhp/collection/HttpConnector2.java | 14 +++-- .../collection/plugin/CollectorPlugin.java | 13 +++- .../mongodb/MongoDbCollectorPlugin.java | 4 +- .../mongodb/MongoDbDumpCollectorPlugin.java | 4 +- .../plugin/oai/OaiCollectorPlugin.java | 4 +- .../collection/plugin/oai/OaiIterator.java | 8 +-- .../plugin/oai/OaiIteratorFactory.java | 4 +- 12 files changed, 123 insertions(+), 56 deletions(-) rename dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/{collection/CollectorPluginReport.java => aggregation/common/AggregatorReport.java} (69%) create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/ReporterCallback.java create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/ReportingJob.java diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorPluginReport.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregatorReport.java similarity index 69% rename from dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorPluginReport.java rename to dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregatorReport.java index d8f167d49..269f8f6e9 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorPluginReport.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregatorReport.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.collection; +package eu.dnetlib.dhp.aggregation.common; import java.io.Closeable; import java.io.IOException; @@ -11,21 +11,20 @@ import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Joiner; import com.google.gson.Gson; import eu.dnetlib.dhp.message.MessageSender; -public class CollectorPluginReport extends LinkedHashMap implements Closeable { +public class AggregatorReport extends LinkedHashMap implements Closeable { - private static final Logger log = LoggerFactory.getLogger(CollectorPluginReport.class); + private static final Logger log = LoggerFactory.getLogger(AggregatorReport.class); private MessageSender messageSender; - public CollectorPluginReport() { + public AggregatorReport() { } - public CollectorPluginReport(MessageSender messageSender) throws IOException { + public AggregatorReport(MessageSender messageSender) throws IOException { this.messageSender = messageSender; } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/ReporterCallback.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/ReporterCallback.java new file mode 100644 index 000000000..b289b6e07 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/ReporterCallback.java @@ -0,0 +1,10 @@ + +package eu.dnetlib.dhp.aggregation.common; + +public interface ReporterCallback { + + Long getCurrent(); + + Long getTotal(); + +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/ReportingJob.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/ReportingJob.java new file mode 100644 index 000000000..791226034 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/ReportingJob.java @@ -0,0 +1,41 @@ + +package eu.dnetlib.dhp.aggregation.common; + +import java.util.TimerTask; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public abstract class ReportingJob { + + /** + * Frequency (seconds) for sending ongoing messages to report the collection task advancement + */ + public static final int ONGOING_REPORT_FREQUENCY = 5; + + /** + * Initial delay (seconds) for sending ongoing messages to report the collection task advancement + */ + public static final int INITIAL_DELAY = 2; + + private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + + protected final AggregatorReport report; + + public ReportingJob(AggregatorReport report) { + this.report = report; + } + + protected void schedule(final ReporterCallback callback) { + executor.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + report.ongoing(callback.getCurrent(), callback.getTotal()); + } + }, INITIAL_DELAY, ONGOING_REPORT_FREQUENCY, TimeUnit.SECONDS); + } + + protected void shutdown() { + executor.shutdown(); + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java index 154b50414..a397c4f9d 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java @@ -5,11 +5,8 @@ import static eu.dnetlib.dhp.common.Constants.SEQUENCE_FILE_NAME; import java.io.IOException; import java.util.Optional; -import java.util.Timer; -import java.util.TimerTask; import java.util.concurrent.atomic.AtomicInteger; -import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; @@ -20,15 +17,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; +import eu.dnetlib.dhp.aggregation.common.AggregatorReport; +import eu.dnetlib.dhp.aggregation.common.ReporterCallback; +import eu.dnetlib.dhp.aggregation.common.ReportingJob; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; import eu.dnetlib.dhp.collection.plugin.mongodb.MongoDbCollectorPlugin; import eu.dnetlib.dhp.collection.plugin.mongodb.MongoDbDumpCollectorPlugin; import eu.dnetlib.dhp.collection.plugin.oai.OaiCollectorPlugin; -public class CollectorWorker { +public class CollectorWorker extends ReportingJob { private static final Logger log = LoggerFactory.getLogger(CollectorWorker.class); - public static final int ONGOING_REPORT_FREQUENCY_MS = 5000; private final ApiDescriptor api; @@ -38,19 +37,17 @@ public class CollectorWorker { private final HttpClientParams clientParams; - private final CollectorPluginReport report; - public CollectorWorker( final ApiDescriptor api, final FileSystem fileSystem, final MDStoreVersion mdStoreVersion, final HttpClientParams clientParams, - final CollectorPluginReport report) { + final AggregatorReport report) { + super(report); this.api = api; this.fileSystem = fileSystem; this.mdStoreVersion = mdStoreVersion; this.clientParams = clientParams; - this.report = report; } public void collect() throws UnknownCollectorPluginException, CollectorException, IOException { @@ -61,13 +58,7 @@ public class CollectorWorker { final CollectorPlugin plugin = getCollectorPlugin(); final AtomicInteger counter = new AtomicInteger(0); - final Timer timer = new Timer(); - timer.schedule(new TimerTask() { - @Override - public void run() { - report.ongoing(counter.longValue(), null); - } - }, 5000, ONGOING_REPORT_FREQUENCY_MS); + scheduleReport(counter); try (SequenceFile.Writer writer = SequenceFile .createWriter( @@ -94,30 +85,46 @@ public class CollectorWorker { report.put(e.getClass().getName(), e.getMessage()); throw new CollectorException(e); } finally { - timer.cancel(); + shutdown(); report.ongoing(counter.longValue(), counter.longValue()); } } + private void scheduleReport(AtomicInteger counter) { + schedule(new ReporterCallback() { + @Override + public Long getCurrent() { + return counter.longValue(); + } + + @Override + public Long getTotal() { + return null; + } + }); + } + private CollectorPlugin getCollectorPlugin() throws UnknownCollectorPluginException { - switch (StringUtils.lowerCase(StringUtils.trim(api.getProtocol()))) { - case "oai": + + switch (CollectorPlugin.NAME.valueOf(api.getProtocol())) { + case oai: return new OaiCollectorPlugin(clientParams); - case "other": - final String plugin = Optional + case other: + final CollectorPlugin.NAME.OTHER_NAME plugin = Optional .ofNullable(api.getParams().get("other_plugin_type")) - .orElseThrow(() -> new UnknownCollectorPluginException("other_plugin_type")); + .map(CollectorPlugin.NAME.OTHER_NAME::valueOf) + .get(); switch (plugin) { - case "mdstore_mongodb_dump": + case mdstore_mongodb_dump: return new MongoDbDumpCollectorPlugin(fileSystem); - case "mdstore_mongodb": + case mdstore_mongodb: return new MongoDbCollectorPlugin(); default: - throw new UnknownCollectorPluginException("Unknown plugin type: " + plugin); + throw new UnknownCollectorPluginException("plugin is not managed: " + plugin); } default: - throw new UnknownCollectorPluginException("Unknown protocol: " + api.getProtocol()); + throw new UnknownCollectorPluginException("protocol is not managed: " + api.getProtocol()); } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorkerApplication.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorkerApplication.java index 15f3f20b5..2c5640499 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorkerApplication.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorkerApplication.java @@ -10,11 +10,11 @@ import java.util.Optional; import org.apache.commons.cli.ParseException; import org.apache.commons.io.IOUtils; import org.apache.hadoop.fs.FileSystem; -import org.apache.hadoop.fs.Path; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; +import eu.dnetlib.dhp.aggregation.common.AggregatorReport; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.message.MessageSender; @@ -80,11 +80,10 @@ public class CollectorWorkerApplication { String dnetMessageManagerURL, String workflowId) throws IOException, CollectorException, UnknownCollectorPluginException { + final MDStoreVersion currentVersion = MAPPER.readValue(mdStoreVersion, MDStoreVersion.class); final MessageSender ms = new MessageSender(dnetMessageManagerURL, workflowId); - final MDStoreVersion currentVersion = MAPPER.readValue(mdStoreVersion, MDStoreVersion.class); - - try (CollectorPluginReport report = new CollectorPluginReport(ms)) { + try (AggregatorReport report = new AggregatorReport(ms)) { new CollectorWorker(api, fileSystem, currentVersion, clientParams, report).collect(); } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/HttpConnector2.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/HttpConnector2.java index 72a2a70a2..ddf9efa36 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/HttpConnector2.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/HttpConnector2.java @@ -15,6 +15,8 @@ import org.apache.http.HttpHeaders; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import eu.dnetlib.dhp.aggregation.common.AggregatorReport; + /** * Migrated from https://svn.driver.research-infrastructures.eu/driver/dnet45/modules/dnet-modular-collector-service/trunk/src/main/java/eu/dnetlib/data/collector/plugins/HttpConnector.java * @@ -42,17 +44,17 @@ public class HttpConnector2 { } /** - * @see HttpConnector2#getInputSource(java.lang.String, CollectorPluginReport) + * @see HttpConnector2#getInputSource(java.lang.String, AggregatorReport) */ public InputStream getInputSourceAsStream(final String requestUrl) throws CollectorException { return IOUtils.toInputStream(getInputSource(requestUrl)); } /** - * @see HttpConnector2#getInputSource(java.lang.String, CollectorPluginReport) + * @see HttpConnector2#getInputSource(java.lang.String, AggregatorReport) */ public String getInputSource(final String requestUrl) throws CollectorException { - return attemptDownloadAsString(requestUrl, 1, new CollectorPluginReport()); + return attemptDownloadAsString(requestUrl, 1, new AggregatorReport()); } /** @@ -63,13 +65,13 @@ public class HttpConnector2 { * @return the content of the downloaded resource * @throws CollectorException when retrying more than maxNumberOfRetry times */ - public String getInputSource(final String requestUrl, CollectorPluginReport report) + public String getInputSource(final String requestUrl, AggregatorReport report) throws CollectorException { return attemptDownloadAsString(requestUrl, 1, report); } private String attemptDownloadAsString(final String requestUrl, final int retryNumber, - final CollectorPluginReport report) throws CollectorException { + final AggregatorReport report) throws CollectorException { try (InputStream s = attemptDownload(requestUrl, retryNumber, report)) { return IOUtils.toString(s); @@ -80,7 +82,7 @@ public class HttpConnector2 { } private InputStream attemptDownload(final String requestUrl, final int retryNumber, - final CollectorPluginReport report) throws CollectorException, IOException { + final AggregatorReport report) throws CollectorException, IOException { if (retryNumber > getClientParams().getMaxNumberOfRetry()) { final String msg = String diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java index 0a4b3a892..0ed6be5fa 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java @@ -3,12 +3,21 @@ package eu.dnetlib.dhp.collection.plugin; import java.util.stream.Stream; +import eu.dnetlib.dhp.aggregation.common.AggregatorReport; import eu.dnetlib.dhp.collection.ApiDescriptor; import eu.dnetlib.dhp.collection.CollectorException; -import eu.dnetlib.dhp.collection.CollectorPluginReport; public interface CollectorPlugin { - Stream collect(ApiDescriptor api, CollectorPluginReport report) throws CollectorException; + enum NAME { + oai, other; + + public enum OTHER_NAME { + mdstore_mongodb_dump, mdstore_mongodb + } + + } + + Stream collect(ApiDescriptor api, AggregatorReport report) throws CollectorException; } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MongoDbCollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MongoDbCollectorPlugin.java index 7d1952f9c..89b92ffa1 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MongoDbCollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MongoDbCollectorPlugin.java @@ -13,9 +13,9 @@ import com.mongodb.MongoClient; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; +import eu.dnetlib.dhp.aggregation.common.AggregatorReport; import eu.dnetlib.dhp.collection.ApiDescriptor; import eu.dnetlib.dhp.collection.CollectorException; -import eu.dnetlib.dhp.collection.CollectorPluginReport; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; public class MongoDbCollectorPlugin implements CollectorPlugin { @@ -26,7 +26,7 @@ public class MongoDbCollectorPlugin implements CollectorPlugin { public static final String MONGODB_DBNAME = "mongodb_dbname"; @Override - public Stream collect(ApiDescriptor api, CollectorPluginReport report) throws CollectorException { + public Stream collect(ApiDescriptor api, AggregatorReport report) throws CollectorException { final String host = Optional .ofNullable(api.getParams().get(MONGODB_HOST)) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MongoDbDumpCollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MongoDbDumpCollectorPlugin.java index d08732593..3199af5b7 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MongoDbDumpCollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MongoDbDumpCollectorPlugin.java @@ -12,9 +12,9 @@ import java.util.zip.GZIPInputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; +import eu.dnetlib.dhp.aggregation.common.AggregatorReport; import eu.dnetlib.dhp.collection.ApiDescriptor; import eu.dnetlib.dhp.collection.CollectorException; -import eu.dnetlib.dhp.collection.CollectorPluginReport; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; import eu.dnetlib.dhp.utils.DHPUtils; @@ -30,7 +30,7 @@ public class MongoDbDumpCollectorPlugin implements CollectorPlugin { } @Override - public Stream collect(ApiDescriptor api, CollectorPluginReport report) throws CollectorException { + public Stream collect(ApiDescriptor api, AggregatorReport report) throws CollectorException { final Path path = Optional .ofNullable(api.getParams().get("path")) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java index 8efdeb838..4600562ca 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiCollectorPlugin.java @@ -13,9 +13,9 @@ import com.google.common.base.Splitter; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; +import eu.dnetlib.dhp.aggregation.common.AggregatorReport; import eu.dnetlib.dhp.collection.ApiDescriptor; import eu.dnetlib.dhp.collection.CollectorException; -import eu.dnetlib.dhp.collection.CollectorPluginReport; import eu.dnetlib.dhp.collection.HttpClientParams; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; @@ -35,7 +35,7 @@ public class OaiCollectorPlugin implements CollectorPlugin { } @Override - public Stream collect(final ApiDescriptor api, final CollectorPluginReport report) + public Stream collect(final ApiDescriptor api, final AggregatorReport report) throws CollectorException { final String baseUrl = api.getBaseUrl(); final String mdFormat = api.getParams().get(FORMAT_PARAM); diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java index 0a0a4c734..887027f21 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java @@ -16,8 +16,8 @@ import org.dom4j.io.SAXReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import eu.dnetlib.dhp.aggregation.common.AggregatorReport; import eu.dnetlib.dhp.collection.CollectorException; -import eu.dnetlib.dhp.collection.CollectorPluginReport; import eu.dnetlib.dhp.collection.HttpConnector2; import eu.dnetlib.dhp.collection.XmlCleaner; @@ -38,7 +38,7 @@ public class OaiIterator implements Iterator { private String token; private boolean started; private final HttpConnector2 httpConnector; - private CollectorPluginReport report; + private AggregatorReport report; public OaiIterator( final String baseUrl, @@ -47,7 +47,7 @@ public class OaiIterator implements Iterator { final String fromDate, final String untilDate, final HttpConnector2 httpConnector, - final CollectorPluginReport report) { + final AggregatorReport report) { this.baseUrl = baseUrl; this.mdFormat = mdFormat; this.set = set; @@ -188,7 +188,7 @@ public class OaiIterator implements Iterator { return doc.valueOf("//*[local-name()='resumptionToken']"); } - public CollectorPluginReport getReport() { + public AggregatorReport getReport() { return report; } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java index a72a62f13..48f6a94c8 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIteratorFactory.java @@ -3,7 +3,7 @@ package eu.dnetlib.dhp.collection.plugin.oai; import java.util.Iterator; -import eu.dnetlib.dhp.collection.CollectorPluginReport; +import eu.dnetlib.dhp.aggregation.common.AggregatorReport; import eu.dnetlib.dhp.collection.HttpClientParams; import eu.dnetlib.dhp.collection.HttpConnector2; @@ -18,7 +18,7 @@ public class OaiIteratorFactory { final String fromDate, final String untilDate, final HttpClientParams clientParams, - final CollectorPluginReport report) { + final AggregatorReport report) { return new OaiIterator(baseUrl, mdFormat, set, fromDate, untilDate, getHttpConnector(clientParams), report); } From 545f8f3e485151c3ac3d865d240c5cd0c4b2f058 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 17 Feb 2021 12:15:00 +0100 Subject: [PATCH 118/445] using jackson objectmapper instead of GSon to serialise the aggregation report --- .../eu/dnetlib/dhp/aggregation/common/AggregatorReport.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregatorReport.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregatorReport.java index 269f8f6e9..9f91c4247 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregatorReport.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregatorReport.java @@ -8,6 +8,7 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; +import eu.dnetlib.dhp.utils.DHPUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,7 +40,7 @@ public class AggregatorReport extends LinkedHashMap implements C this.forEach((k, v) -> log.info("{} - {}", k, v)); Map m = new HashMap<>(); - m.put(getClass().getSimpleName().toLowerCase(), new Gson().toJson(values())); + m.put(getClass().getSimpleName().toLowerCase(), DHPUtils.MAPPER.writeValueAsString(values())); messageSender.sendReport(m); } } From cc88701f29eaea235fe596c65d7815a048e49645 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 17 Feb 2021 16:13:54 +0100 Subject: [PATCH 119/445] retry for any Socket exception --- .../main/java/eu/dnetlib/dhp/collection/HttpConnector2.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/HttpConnector2.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/HttpConnector2.java index ddf9efa36..9d8b8d34b 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/HttpConnector2.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/HttpConnector2.java @@ -176,11 +176,11 @@ public class HttpConnector2 { .format( "Unexpected status code: %s errors: %s", urlConn.getResponseCode(), MAPPER.writeValueAsString(report))); - } catch (MalformedURLException | SocketException | UnknownHostException e) { + } catch (MalformedURLException | UnknownHostException e) { log.error(e.getMessage(), e); report.put(e.getClass().getName(), e.getMessage()); throw new CollectorException(e.getMessage(), e); - } catch (SocketTimeoutException e) { + } catch (SocketTimeoutException | SocketException e) { log.error(e.getMessage(), e); report.put(e.getClass().getName(), e.getMessage()); backoffAndSleep(getClientParams().getRetryDelay() * retryNumber * 1000); From 58467aaf1eef7043688cf49d2c5cc1ffd745dca3 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 17 Feb 2021 16:14:41 +0100 Subject: [PATCH 120/445] WIP: transformation workflow error reporting --- .../aggregation/common/AggregatorReport.java | 2 +- .../transformation/TransformSparkJobNode.java | 54 +++++++++++++------ .../dhp/transformation/oozie_app/workflow.xml | 10 ++++ .../transformation_input_parameters.json | 15 ++++-- 4 files changed, 61 insertions(+), 20 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregatorReport.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregatorReport.java index 9f91c4247..c822a6723 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregatorReport.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/aggregation/common/AggregatorReport.java @@ -8,13 +8,13 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; -import eu.dnetlib.dhp.utils.DHPUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.Gson; import eu.dnetlib.dhp.message.MessageSender; +import eu.dnetlib.dhp.utils.DHPUtils; public class AggregatorReport extends LinkedHashMap implements Closeable { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java index f9a18987d..0b3de6490 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java @@ -21,11 +21,14 @@ import org.slf4j.LoggerFactory; import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; import eu.dnetlib.dhp.aggregation.common.AggregationCounter; +import eu.dnetlib.dhp.aggregation.common.AggregatorReport; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; +import eu.dnetlib.dhp.message.MessageSender; import eu.dnetlib.dhp.model.mdstore.MetadataRecord; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import parquet.hadoop.ParquetReader; public class TransformSparkJobNode { @@ -54,7 +57,7 @@ public class TransformSparkJobNode { final MDStoreVersion nativeMdStoreVersion = MAPPER.readValue(mdstoreInputVersion, MDStoreVersion.class); final String inputPath = nativeMdStoreVersion.getHdfsPath() + MDSTORE_DATA_PATH; log.info("inputPath: {}", inputPath); - + ParquetReader final MDStoreVersion cleanedMdStoreVersion = MAPPER.readValue(mdstoreOutputVersion, MDStoreVersion.class); final String outputBasePath = cleanedMdStoreVersion.getHdfsPath(); log.info("outputBasePath: {}", outputBasePath); @@ -91,23 +94,42 @@ public class TransformSparkJobNode { final AggregationCounter ct = new AggregationCounter(totalItems, errorItems, transformedItems); final Encoder encoder = Encoders.bean(MetadataRecord.class); - final Dataset mdstore = spark - .read() - .format("parquet") - .load(inputPath) - .as(encoder) - .map( - TransformationFactory.getTransformationPlugin(args, ct, isLookUpService), - encoder); - saveDataset(mdstore, outputBasePath + MDSTORE_DATA_PATH); + final String dnetMessageManagerURL = args.get(DNET_MESSAGE_MGR_URL); + log.info("dnetMessageManagerURL is {}", dnetMessageManagerURL); - log.info("Transformed item " + ct.getProcessedItems().count()); - log.info("Total item " + ct.getTotalItems().count()); - log.info("Transformation Error item " + ct.getErrorItems().count()); + final String workflowId = args.get("workflowId"); + log.info("workflowId is {}", workflowId); - writeHdfsFile( - spark.sparkContext().hadoopConfiguration(), - "" + spark.read().load(outputBasePath + MDSTORE_DATA_PATH).count(), outputBasePath + MDSTORE_SIZE_PATH); + final MessageSender messageSender = new MessageSender(dnetMessageManagerURL, workflowId); + try (AggregatorReport report = new AggregatorReport(messageSender)) { + try { + final Dataset mdstore = spark + .read() + .format("parquet") + .load(inputPath) + .as(encoder) + .map( + TransformationFactory.getTransformationPlugin(args, ct, isLookUpService), + encoder); + saveDataset(mdstore, outputBasePath + MDSTORE_DATA_PATH); + + log.info("Transformed item " + ct.getProcessedItems().count()); + log.info("Total item " + ct.getTotalItems().count()); + log.info("Transformation Error item " + ct.getErrorItems().count()); + + final long mdStoreSize = spark.read().load(outputBasePath + MDSTORE_DATA_PATH).count(); + writeHdfsFile( + spark.sparkContext().hadoopConfiguration(), + "" + mdStoreSize, outputBasePath + MDSTORE_SIZE_PATH); + } catch (Throwable e) { + log.error("error during record transformation", e); + report.put(TransformSparkJobNode.class.getSimpleName(), e.getMessage()); + report.put(CONTENT_TOTALITEMS, ct.getTotalItems().value().toString()); + report.put(CONTENT_INVALIDRECORDS, ct.getErrorItems().value().toString()); + report.put(CONTENT_TRANSFORMEDRECORDS, ct.getProcessedItems().value().toString()); + throw e; + } + } } } diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml index 9e01936d4..61e5710fa 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml @@ -29,6 +29,14 @@ isLookupUrl The IS lookUp service endopoint
+ + workflowId + The identifier of the workflow + + + dnetMessageManagerURL + The URI of the Dnet Message Manager + @@ -95,6 +103,8 @@ --transformationPlugin${transformationPlugin} --transformationRuleId${transformationRuleId} --isLookupUrl${isLookupUrl} + --workflowId${workflowId} + --dnetMessageManagerURL${dnetMessageManagerURL} diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/transformation_input_parameters.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/transformation_input_parameters.json index d92698de5..ee9099dde 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/transformation_input_parameters.json +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/transformation_input_parameters.json @@ -36,9 +36,18 @@ "paramDescription": "the Information System Service LookUp URL", "paramRequired": true }, - - - + { + "paramName": "dm", + "paramLongName": "dnetMessageManagerURL", + "paramDescription": "the End point URL to send Messages", + "paramRequired": true + }, + { + "paramName": "w", + "paramLongName": "workflowId", + "paramDescription": "the identifier of the dnet Workflow", + "paramRequired": true + }, { "paramName": "tp", "paramLongName": "transformationPlugin", From e7eba9f7e71afd638ad00dcb2d1b83521aa74719 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 17 Feb 2021 16:54:08 +0100 Subject: [PATCH 121/445] WIP: transformation workflow error reporting; cleanup --- .../eu/dnetlib/dhp/transformation/TransformSparkJobNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java index 0b3de6490..cc130c376 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java @@ -57,7 +57,7 @@ public class TransformSparkJobNode { final MDStoreVersion nativeMdStoreVersion = MAPPER.readValue(mdstoreInputVersion, MDStoreVersion.class); final String inputPath = nativeMdStoreVersion.getHdfsPath() + MDSTORE_DATA_PATH; log.info("inputPath: {}", inputPath); - ParquetReader + final MDStoreVersion cleanedMdStoreVersion = MAPPER.readValue(mdstoreOutputVersion, MDStoreVersion.class); final String outputBasePath = cleanedMdStoreVersion.getHdfsPath(); log.info("outputBasePath: {}", outputBasePath); From fc3fa5e3436db1c1144afa9703e2d5e3a4e4a9c2 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 24 Feb 2021 15:07:24 +0100 Subject: [PATCH 122/445] implemented mdstore collector plugin --- dhp-common/pom.xml | 4 +++ .../eu/dnetlib/dhp}/common/MdstoreClient.java | 15 +++++++- .../dhp/common/rest/DNetRestClient.java | 15 ++++++++ .../dhp/collection/CollectorWorker.java | 4 +-- ...lugin.java => MDStoreCollectorPlugin.java} | 36 +++++++------------ .../raw/MigrateMongoMdstoresApplication.java | 2 +- 6 files changed, 49 insertions(+), 27 deletions(-) rename {dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw => dhp-common/src/main/java/eu/dnetlib/dhp}/common/MdstoreClient.java (84%) rename dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/{MongoDbCollectorPlugin.java => MDStoreCollectorPlugin.java} (54%) diff --git a/dhp-common/pom.xml b/dhp-common/pom.xml index e2db8b451..7c8be8d3e 100644 --- a/dhp-common/pom.xml +++ b/dhp-common/pom.xml @@ -98,6 +98,10 @@ httpclient + + org.mongodb + mongo-java-driver + eu.dnetlib.dhp diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/MdstoreClient.java b/dhp-common/src/main/java/eu/dnetlib/dhp/common/MdstoreClient.java similarity index 84% rename from dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/MdstoreClient.java rename to dhp-common/src/main/java/eu/dnetlib/dhp/common/MdstoreClient.java index a2177935a..236e4d8b0 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/MdstoreClient.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/common/MdstoreClient.java @@ -1,13 +1,16 @@ -package eu.dnetlib.dhp.oa.graph.raw.common; +package eu.dnetlib.dhp.common; import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.stream.StreamSupport; +import com.mongodb.BasicDBObject; +import com.mongodb.QueryBuilder; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -34,6 +37,16 @@ public class MdstoreClient implements Closeable { this.db = getDb(client, dbName); } + public MongoCollection mdStore(final String mdId) { + BasicDBObject query = (BasicDBObject) QueryBuilder.start("mdId").is(mdId).get(); + + final String currentId = Optional.ofNullable(getColl(db, COLL_METADATA_MANAGER, true).find(query)) + .map(r -> r.first()) + .map(d -> d.getString("currentId")) + .orElseThrow(() -> new IllegalArgumentException("cannot find current mdstore id for: " + mdId)); + return getColl(db, currentId, true); + } + public Map validCollections( final String mdFormat, final String mdLayout, final String mdInterpretation) { diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/common/rest/DNetRestClient.java b/dhp-common/src/main/java/eu/dnetlib/dhp/common/rest/DNetRestClient.java index 014f18606..27713d9b5 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/common/rest/DNetRestClient.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/common/rest/DNetRestClient.java @@ -11,9 +11,16 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.stream.Collectors; public class DNetRestClient { + private static final Logger log = LoggerFactory.getLogger(DNetRestClient.class); + private static ObjectMapper mapper = new ObjectMapper(); public static T doGET(final String url, Class clazz) throws Exception { @@ -44,6 +51,14 @@ public class DNetRestClient { private static String doHTTPRequest(final HttpUriRequest r) throws Exception { CloseableHttpClient client = HttpClients.createDefault(); + + log.info("performing HTTP request, method {} on URI {}", r.getMethod(), r.getURI().toString()); + log.info("request headers: {}", + Arrays.asList(r.getAllHeaders()) + .stream() + .map(h -> h.getName() + ":" + h.getValue()) + .collect(Collectors.joining(","))); + CloseableHttpResponse response = client.execute(r); return IOUtils.toString(response.getEntity().getContent()); } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java index a397c4f9d..ef29cb5b1 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java @@ -21,7 +21,7 @@ import eu.dnetlib.dhp.aggregation.common.AggregatorReport; import eu.dnetlib.dhp.aggregation.common.ReporterCallback; import eu.dnetlib.dhp.aggregation.common.ReportingJob; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; -import eu.dnetlib.dhp.collection.plugin.mongodb.MongoDbCollectorPlugin; +import eu.dnetlib.dhp.collection.plugin.mongodb.MDStoreCollectorPlugin; import eu.dnetlib.dhp.collection.plugin.mongodb.MongoDbDumpCollectorPlugin; import eu.dnetlib.dhp.collection.plugin.oai.OaiCollectorPlugin; @@ -119,7 +119,7 @@ public class CollectorWorker extends ReportingJob { case mdstore_mongodb_dump: return new MongoDbDumpCollectorPlugin(fileSystem); case mdstore_mongodb: - return new MongoDbCollectorPlugin(); + return new MDStoreCollectorPlugin(); default: throw new UnknownCollectorPluginException("plugin is not managed: " + plugin); } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MongoDbCollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MDStoreCollectorPlugin.java similarity index 54% rename from dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MongoDbCollectorPlugin.java rename to dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MDStoreCollectorPlugin.java index 89b92ffa1..33b9111dd 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MongoDbCollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MDStoreCollectorPlugin.java @@ -7,48 +7,38 @@ import java.util.Spliterators; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import org.bson.Document; - -import com.mongodb.MongoClient; import com.mongodb.client.MongoCollection; -import com.mongodb.client.MongoDatabase; +import eu.dnetlib.dhp.common.MdstoreClient; import eu.dnetlib.dhp.aggregation.common.AggregatorReport; import eu.dnetlib.dhp.collection.ApiDescriptor; import eu.dnetlib.dhp.collection.CollectorException; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; +import org.bson.Document; -public class MongoDbCollectorPlugin implements CollectorPlugin { +public class MDStoreCollectorPlugin implements CollectorPlugin { - public static final String MONGODB_HOST = "mongodb_host"; - public static final String MONGODB_PORT = "mongodb_port"; - public static final String MONGODB_COLLECTION = "mongodb_collection"; + public static final String MONGODB_BASEURL = "mongodb_baseurl"; public static final String MONGODB_DBNAME = "mongodb_dbname"; + public static final String MDSTORE_ID = "mongodb_collection"; @Override public Stream collect(ApiDescriptor api, AggregatorReport report) throws CollectorException { - final String host = Optional - .ofNullable(api.getParams().get(MONGODB_HOST)) - .orElseThrow(() -> new CollectorException(String.format("missing parameter '%s'", MONGODB_HOST))); - - final Integer port = Optional - .ofNullable(api.getParams().get(MONGODB_PORT)) - .map(Integer::parseInt) - .orElseThrow(() -> new CollectorException(String.format("missing parameter '%s'", MONGODB_PORT))); + final String mongoBaseUrl = Optional + .ofNullable(api.getParams().get(MONGODB_BASEURL)) + .orElseThrow(() -> new CollectorException(String.format("missing parameter '%s'", MONGODB_BASEURL))); final String dbName = Optional .ofNullable(api.getParams().get(MONGODB_DBNAME)) .orElseThrow(() -> new CollectorException(String.format("missing parameter '%s'", MONGODB_DBNAME))); - final String collection = Optional - .ofNullable(api.getParams().get(MONGODB_COLLECTION)) - .orElseThrow(() -> new CollectorException(String.format("missing parameter '%s'", MONGODB_COLLECTION))); - - final MongoClient mongoClient = new MongoClient(host, port); - final MongoDatabase database = mongoClient.getDatabase(dbName); - final MongoCollection mdstore = database.getCollection(collection); + final String mdId = Optional + .ofNullable(api.getParams().get(MDSTORE_ID)) + .orElseThrow(() -> new CollectorException(String.format("missing parameter '%s'", MDSTORE_ID))); + final MdstoreClient client = new MdstoreClient(mongoBaseUrl, dbName); + final MongoCollection mdstore = client.mdStore(mdId); long size = mdstore.count(); return StreamSupport diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateMongoMdstoresApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateMongoMdstoresApplication.java index e7703bf72..9e7e051de 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateMongoMdstoresApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateMongoMdstoresApplication.java @@ -12,7 +12,7 @@ import org.apache.commons.logging.LogFactory; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.oa.graph.raw.common.AbstractMigrationApplication; -import eu.dnetlib.dhp.oa.graph.raw.common.MdstoreClient; +import eu.dnetlib.dhp.common.MdstoreClient; public class MigrateMongoMdstoresApplication extends AbstractMigrationApplication implements Closeable { From 9c899f44335278053fb47524dd112ff476a15488 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 24 Feb 2021 15:07:59 +0100 Subject: [PATCH 123/445] cleanup on transformation functions and the relative tests --- .../dhp/transformation/xslt/Cleaner.java | 7 +- .../dhp/transformation/xslt/DateCleaner.java | 6 +- .../xslt/XSLTTransformationFunction.java | 2 + .../transformation/TransformationJobTest.java | 97 +++++++------------ .../eu/dnetlib/dhp/transform/ext_simple.xsl | 2 +- .../eu/dnetlib/dhp/transform/zenodo_tr.xslt | 4 +- 6 files changed, 51 insertions(+), 67 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java index 124f68325..50ffd304b 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java @@ -4,7 +4,10 @@ package eu.dnetlib.dhp.transformation.xslt; import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.schema.oaf.Qualifier; import net.sf.saxon.s9api.*; -import scala.Serializable; + +import java.io.Serializable; + +import static eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction.QNAME_BASE_URI; public class Cleaner implements ExtensionFunction, Serializable { @@ -16,7 +19,7 @@ public class Cleaner implements ExtensionFunction, Serializable { @Override public QName getName() { - return new QName("http://eu/dnetlib/transform/extension", "clean"); + return new QName(QNAME_BASE_URI + "/clean", "clean"); } @Override diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/DateCleaner.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/DateCleaner.java index 4e1a29b52..479dd9854 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/DateCleaner.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/DateCleaner.java @@ -1,6 +1,7 @@ package eu.dnetlib.dhp.transformation.xslt; +import java.io.Serializable; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.*; @@ -8,7 +9,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sf.saxon.s9api.*; -import scala.Serializable; + +import static eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction.QNAME_BASE_URI; public class DateCleaner implements ExtensionFunction, Serializable { @@ -91,7 +93,7 @@ public class DateCleaner implements ExtensionFunction, Serializable { @Override public QName getName() { - return new QName("http://eu/dnetlib/trasform/dates", "dateISO"); + return new QName(QNAME_BASE_URI + "/dateISO", "dateISO"); } @Override diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java index 7d47cc84d..a813d84db 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java @@ -15,6 +15,8 @@ import net.sf.saxon.s9api.*; public class XSLTTransformationFunction implements MapFunction { + public final static String QNAME_BASE_URI = "http://eu/dnetlib/transform"; + private final AggregationCounter aggregationCounter; private final String transformationRule; diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java index 091089eb9..50aa2ea08 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java @@ -5,7 +5,6 @@ import static eu.dnetlib.dhp.common.Constants.MDSTORE_DATA_PATH; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; import java.util.stream.Collectors; @@ -35,26 +34,11 @@ import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; @ExtendWith(MockitoExtension.class) public class TransformationJobTest extends AbstractVocabularyTest { - private static SparkSession spark; - - @BeforeAll - public static void beforeAll() throws IOException, ISLookUpException { - SparkConf conf = new SparkConf(); - conf.setAppName(TransformationJobTest.class.getSimpleName()); - conf.setMaster("local"); - spark = SparkSession.builder().config(conf).getOrCreate(); - } - @BeforeEach public void setUp() throws IOException, ISLookUpException { setUpVocabulary(); } - @AfterAll - public static void afterAll() { - spark.stop(); - } - @Test @DisplayName("Test Date cleaner") public void testDateCleaner() throws Exception { @@ -82,68 +66,61 @@ public class TransformationJobTest extends AbstractVocabularyTest { // Print the record System.out.println(result.getBody()); // TODO Create significant Assert - } - @DisplayName("Test TransformSparkJobNode.main") @Test + @DisplayName("Test TransformSparkJobNode.main") public void transformTest(@TempDir Path testDir) throws Exception { - final String mdstore_input = this.getClass().getResource("/eu/dnetlib/dhp/transform/mdstorenative").getFile(); - final String mdstore_output = testDir.toString() + "/version"; + SparkConf conf = new SparkConf(); + conf.setAppName(TransformationJobTest.class.getSimpleName()); + conf.setMaster("local"); - mockupTrasformationRule("simpleTRule", "/eu/dnetlib/dhp/transform/ext_simple.xsl"); + try(SparkSession spark = SparkSession.builder().config(conf).getOrCreate()) { - final Map parameters = Stream.of(new String[][] { - { - "dateOfTransformation", "1234" - }, - { - "transformationPlugin", "XSLT_TRANSFORM" - }, - { - "transformationRuleId", "simpleTRule" - }, + final String mdstore_input = this.getClass().getResource("/eu/dnetlib/dhp/transform/mdstorenative").getFile(); + final String mdstore_output = testDir.toString() + "/version"; - }).collect(Collectors.toMap(data -> data[0], data -> data[1])); + mockupTrasformationRule("simpleTRule", "/eu/dnetlib/dhp/transform/ext_simple.xsl"); - TransformSparkJobNode.transformRecords(parameters, isLookUpService, spark, mdstore_input, mdstore_output); + final Map parameters = Stream.of(new String[][]{ + { + "dateOfTransformation", "1234" + }, + { + "transformationPlugin", "XSLT_TRANSFORM" + }, + { + "transformationRuleId", "simpleTRule" + }, - // TODO introduce useful assertions + }).collect(Collectors.toMap(data -> data[0], data -> data[1])); - final Encoder encoder = Encoders.bean(MetadataRecord.class); - final Dataset mOutput = spark - .read() - .format("parquet") - .load(mdstore_output + MDSTORE_DATA_PATH) - .as(encoder); + TransformSparkJobNode.transformRecords(parameters, isLookUpService, spark, mdstore_input, mdstore_output); - final Long total = mOutput.count(); + // TODO introduce useful assertions - final long recordTs = mOutput - .filter((FilterFunction) p -> p.getDateOfTransformation() == 1234) - .count(); + final Encoder encoder = Encoders.bean(MetadataRecord.class); + final Dataset mOutput = spark + .read() + .format("parquet") + .load(mdstore_output + MDSTORE_DATA_PATH) + .as(encoder); - final long recordNotEmpty = mOutput - .filter((FilterFunction) p -> !StringUtils.isBlank(p.getBody())) - .count(); + final Long total = mOutput.count(); - assertEquals(total, recordTs); + final long recordTs = mOutput + .filter((FilterFunction) p -> p.getDateOfTransformation() == 1234) + .count(); - assertEquals(total, recordNotEmpty); + final long recordNotEmpty = mOutput + .filter((FilterFunction) p -> !StringUtils.isBlank(p.getBody())) + .count(); - } + assertEquals(total, recordTs); - @Test - public void tryLoadFolderOnCP() throws Exception { - final String path = this.getClass().getResource("/eu/dnetlib/dhp/transform/mdstorenative").getFile(); - System.out.println("path = " + path); - - Path tempDirWithPrefix = Files.createTempDirectory("mdstore_output"); - - System.out.println(tempDirWithPrefix.toFile().getAbsolutePath()); - - Files.deleteIfExists(tempDirWithPrefix); + assertEquals(total, recordNotEmpty); + } } private XSLTTransformationFunction loadTransformationRule(final String path) throws Exception { diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl index e2a439315..8f8ce2270 100644 --- a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/ext_simple.xsl @@ -1,7 +1,7 @@ diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/zenodo_tr.xslt b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/zenodo_tr.xslt index 23e57579b..9a02c9071 100644 --- a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/zenodo_tr.xslt +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/zenodo_tr.xslt @@ -3,8 +3,8 @@ xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:oai="http://www.openarchives.org/OAI/2.0/" xmlns:oaf="http://namespace.openaire.eu/oaf" - xmlns:vocabulary="http://eu/dnetlib/trasform/extension" - xmlns:dateCleaner="http://eu/dnetlib/trasform/dates" + xmlns:vocabulary="http://eu/dnetlib/transform/clean" + xmlns:dateCleaner="http://eu/dnetlib/transform/dateISO" xmlns:dr="http://www.driver-repository.eu/namespace/dr" exclude-result-prefixes="xsl vocabulary dateCleaner"> From 271e88537bc29089611b4c3b5b14b7b552ee3eef Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 25 Feb 2021 12:28:56 +0100 Subject: [PATCH 124/445] code formatting --- .../dhp/transformation/xslt/Cleaner.java | 8 ++-- .../dhp/transformation/xslt/DateCleaner.java | 4 +- .../transformation/TransformationJobTest.java | 43 ++++++++++--------- 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java index 50ffd304b..664215c0e 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/Cleaner.java @@ -1,14 +1,14 @@ package eu.dnetlib.dhp.transformation.xslt; +import static eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction.QNAME_BASE_URI; + +import java.io.Serializable; + import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.schema.oaf.Qualifier; import net.sf.saxon.s9api.*; -import java.io.Serializable; - -import static eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction.QNAME_BASE_URI; - public class Cleaner implements ExtensionFunction, Serializable { private final VocabularyGroup vocabularies; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/DateCleaner.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/DateCleaner.java index 479dd9854..6e337604f 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/DateCleaner.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/DateCleaner.java @@ -1,6 +1,8 @@ package eu.dnetlib.dhp.transformation.xslt; +import static eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction.QNAME_BASE_URI; + import java.io.Serializable; import java.time.LocalDate; import java.time.format.DateTimeFormatter; @@ -10,8 +12,6 @@ import java.util.regex.Pattern; import net.sf.saxon.s9api.*; -import static eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction.QNAME_BASE_URI; - public class DateCleaner implements ExtensionFunction, Serializable { private final static List dateRegex = Arrays diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java index 50aa2ea08..3c0c8bf0f 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java @@ -76,23 +76,26 @@ public class TransformationJobTest extends AbstractVocabularyTest { conf.setAppName(TransformationJobTest.class.getSimpleName()); conf.setMaster("local"); - try(SparkSession spark = SparkSession.builder().config(conf).getOrCreate()) { + try (SparkSession spark = SparkSession.builder().config(conf).getOrCreate()) { - final String mdstore_input = this.getClass().getResource("/eu/dnetlib/dhp/transform/mdstorenative").getFile(); + final String mdstore_input = this + .getClass() + .getResource("/eu/dnetlib/dhp/transform/mdstorenative") + .getFile(); final String mdstore_output = testDir.toString() + "/version"; mockupTrasformationRule("simpleTRule", "/eu/dnetlib/dhp/transform/ext_simple.xsl"); - final Map parameters = Stream.of(new String[][]{ - { - "dateOfTransformation", "1234" - }, - { - "transformationPlugin", "XSLT_TRANSFORM" - }, - { - "transformationRuleId", "simpleTRule" - }, + final Map parameters = Stream.of(new String[][] { + { + "dateOfTransformation", "1234" + }, + { + "transformationPlugin", "XSLT_TRANSFORM" + }, + { + "transformationRuleId", "simpleTRule" + }, }).collect(Collectors.toMap(data -> data[0], data -> data[1])); @@ -102,20 +105,20 @@ public class TransformationJobTest extends AbstractVocabularyTest { final Encoder encoder = Encoders.bean(MetadataRecord.class); final Dataset mOutput = spark - .read() - .format("parquet") - .load(mdstore_output + MDSTORE_DATA_PATH) - .as(encoder); + .read() + .format("parquet") + .load(mdstore_output + MDSTORE_DATA_PATH) + .as(encoder); final Long total = mOutput.count(); final long recordTs = mOutput - .filter((FilterFunction) p -> p.getDateOfTransformation() == 1234) - .count(); + .filter((FilterFunction) p -> p.getDateOfTransformation() == 1234) + .count(); final long recordNotEmpty = mOutput - .filter((FilterFunction) p -> !StringUtils.isBlank(p.getBody())) - .count(); + .filter((FilterFunction) p -> !StringUtils.isBlank(p.getBody())) + .count(); assertEquals(total, recordTs); From dc98c39500a74fb55d5e951d80f1c38bcb286ec4 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 25 Feb 2021 12:29:18 +0100 Subject: [PATCH 125/445] more logging --- .../dhp/common/rest/DNetRestClient.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/common/rest/DNetRestClient.java b/dhp-common/src/main/java/eu/dnetlib/dhp/common/rest/DNetRestClient.java index 27713d9b5..853d22bc2 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/common/rest/DNetRestClient.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/common/rest/DNetRestClient.java @@ -1,6 +1,9 @@ package eu.dnetlib.dhp.common.rest; +import java.util.Arrays; +import java.util.stream.Collectors; + import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; @@ -9,13 +12,10 @@ import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; - -import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Arrays; -import java.util.stream.Collectors; +import com.fasterxml.jackson.databind.ObjectMapper; public class DNetRestClient { @@ -53,11 +53,14 @@ public class DNetRestClient { CloseableHttpClient client = HttpClients.createDefault(); log.info("performing HTTP request, method {} on URI {}", r.getMethod(), r.getURI().toString()); - log.info("request headers: {}", - Arrays.asList(r.getAllHeaders()) - .stream() - .map(h -> h.getName() + ":" + h.getValue()) - .collect(Collectors.joining(","))); + log + .info( + "request headers: {}", + Arrays + .asList(r.getAllHeaders()) + .stream() + .map(h -> h.getName() + ":" + h.getValue()) + .collect(Collectors.joining(","))); CloseableHttpResponse response = client.execute(r); return IOUtils.toString(response.getEntity().getContent()); From b830e333922e9b421c4955c07d03c3b5aad4fc75 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 25 Feb 2021 12:30:30 +0100 Subject: [PATCH 126/445] mdstore collector plugin --- .../java/eu/dnetlib/dhp/common/MdstoreClient.java | 13 +++++++------ .../plugin/mongodb/MDStoreCollectorPlugin.java | 14 ++++++++------ .../graph/raw/MigrateMongoMdstoresApplication.java | 2 +- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/common/MdstoreClient.java b/dhp-common/src/main/java/eu/dnetlib/dhp/common/MdstoreClient.java index 236e4d8b0..d29498306 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/common/MdstoreClient.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/common/MdstoreClient.java @@ -9,16 +9,16 @@ import java.util.Map; import java.util.Optional; import java.util.stream.StreamSupport; -import com.mongodb.BasicDBObject; -import com.mongodb.QueryBuilder; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.bson.Document; import com.google.common.collect.Iterables; +import com.mongodb.BasicDBObject; import com.mongodb.MongoClient; import com.mongodb.MongoClientURI; +import com.mongodb.QueryBuilder; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; @@ -40,10 +40,11 @@ public class MdstoreClient implements Closeable { public MongoCollection mdStore(final String mdId) { BasicDBObject query = (BasicDBObject) QueryBuilder.start("mdId").is(mdId).get(); - final String currentId = Optional.ofNullable(getColl(db, COLL_METADATA_MANAGER, true).find(query)) - .map(r -> r.first()) - .map(d -> d.getString("currentId")) - .orElseThrow(() -> new IllegalArgumentException("cannot find current mdstore id for: " + mdId)); + final String currentId = Optional + .ofNullable(getColl(db, COLL_METADATA_MANAGER, true).find(query)) + .map(r -> r.first()) + .map(d -> d.getString("currentId")) + .orElseThrow(() -> new IllegalArgumentException("cannot find current mdstore id for: " + mdId)); return getColl(db, currentId, true); } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MDStoreCollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MDStoreCollectorPlugin.java index 33b9111dd..77e899cc9 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MDStoreCollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MDStoreCollectorPlugin.java @@ -7,27 +7,29 @@ import java.util.Spliterators; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import org.bson.Document; + import com.mongodb.client.MongoCollection; -import eu.dnetlib.dhp.common.MdstoreClient; import eu.dnetlib.dhp.aggregation.common.AggregatorReport; import eu.dnetlib.dhp.collection.ApiDescriptor; import eu.dnetlib.dhp.collection.CollectorException; import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; -import org.bson.Document; +import eu.dnetlib.dhp.common.MdstoreClient; public class MDStoreCollectorPlugin implements CollectorPlugin { - public static final String MONGODB_BASEURL = "mongodb_baseurl"; public static final String MONGODB_DBNAME = "mongodb_dbname"; - public static final String MDSTORE_ID = "mongodb_collection"; + public static final String MDSTORE_ID = "mdstore_id"; @Override public Stream collect(ApiDescriptor api, AggregatorReport report) throws CollectorException { final String mongoBaseUrl = Optional - .ofNullable(api.getParams().get(MONGODB_BASEURL)) - .orElseThrow(() -> new CollectorException(String.format("missing parameter '%s'", MONGODB_BASEURL))); + .ofNullable(api.getBaseUrl()) + .orElseThrow( + () -> new CollectorException( + "missing mongodb baseUrl, expected in eu.dnetlib.dhp.collection.ApiDescriptor.baseUrl")); final String dbName = Optional .ofNullable(api.getParams().get(MONGODB_DBNAME)) diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateMongoMdstoresApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateMongoMdstoresApplication.java index 9e7e051de..50042b569 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateMongoMdstoresApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateMongoMdstoresApplication.java @@ -11,8 +11,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.oa.graph.raw.common.AbstractMigrationApplication; import eu.dnetlib.dhp.common.MdstoreClient; +import eu.dnetlib.dhp.oa.graph.raw.common.AbstractMigrationApplication; public class MigrateMongoMdstoresApplication extends AbstractMigrationApplication implements Closeable { From 7df2461ccc8b3a1362463cdb9f84613a9c45e31d Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 25 Feb 2021 16:19:12 +0100 Subject: [PATCH 127/445] indent XML records collected from oai-pmh endpoints --- .../collection/plugin/oai/OaiIterator.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java index 887027f21..65695fe8e 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/oai/OaiIterator.java @@ -1,7 +1,9 @@ package eu.dnetlib.dhp.collection.plugin.oai; +import java.io.IOException; import java.io.StringReader; +import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Iterator; @@ -11,8 +13,11 @@ import java.util.concurrent.PriorityBlockingQueue; import org.apache.commons.lang.StringUtils; import org.dom4j.Document; import org.dom4j.DocumentException; +import org.dom4j.DocumentHelper; import org.dom4j.Node; +import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; +import org.dom4j.io.XMLWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,7 +33,6 @@ public class OaiIterator implements Iterator { private final static String REPORT_PREFIX = "oai:"; private final Queue queue = new PriorityBlockingQueue<>(); - private final SAXReader reader = new SAXReader(); private final String baseUrl; private final String set; @@ -149,13 +153,13 @@ public class OaiIterator implements Iterator { final String xml = httpConnector.getInputSource(url, report); Document doc; try { - doc = reader.read(new StringReader(xml)); + doc = DocumentHelper.parseText(xml); } catch (final DocumentException e) { log.warn("Error parsing xml, I try to clean it. {}", e.getMessage()); report.put(e.getClass().getName(), e.getMessage()); final String cleaned = XmlCleaner.cleanAllEntities(xml); try { - doc = reader.read(new StringReader(cleaned)); + doc = DocumentHelper.parseText(xml); } catch (final DocumentException e1) { final String resumptionToken = extractResumptionToken(xml); if (resumptionToken == null) { @@ -182,7 +186,15 @@ public class OaiIterator implements Iterator { } for (final Object o : doc.selectNodes("//*[local-name()='ListRecords']/*[local-name()='record']")) { - queue.add(((Node) o).asXML()); + final StringWriter sw = new StringWriter(); + final XMLWriter writer = new XMLWriter(sw, OutputFormat.createPrettyPrint()); + try { + writer.write((Node) o); + queue.add(sw.toString()); + } catch (IOException e) { + report.put(e.getClass().getName(), e.getMessage()); + throw new CollectorException("Error parsing XML record:\n" + ((Node) o).asXML(), e); + } } return doc.valueOf("//*[local-name()='resumptionToken']"); From 1a85020572db5f9e8084a4ff84b11df59643ed5d Mon Sep 17 00:00:00 2001 From: miconis Date: Fri, 26 Feb 2021 10:19:28 +0100 Subject: [PATCH 128/445] bug fix in graph-mapper, changes in the implementation of the openorgs wf to create relations and populate openorgs db --- dhp-workflows/dhp-dedup-openaire/pom.xml | 5 +- .../dhp/oa/dedup/AbstractSparkAction.java | 1 + .../eu/dnetlib/dhp/oa/dedup/DedupUtility.java | 5 + .../dhp/oa/dedup/SparkCopyOpenorgs.java | 31 +- .../oa/dedup/SparkCopyOpenorgsMergeRels.java | 37 +- .../oa/dedup/SparkCopyOpenorgsSimRels.java | 145 ++++---- .../dedup/SparkCopyRelationsNoOpenorgs.java | 144 ++++---- .../dhp/oa/dedup/SparkCreateSimRels.java | 22 +- .../dhp/oa/dedup/SparkPrepareNewOrgs.java | 249 +++++++++++++ .../dhp/oa/dedup/SparkPrepareOrgRels.java | 341 ++++++++++++++++++ .../dhp/oa/dedup/SparkRemoveDiffRels.java | 131 ++++--- .../dnetlib/dhp/oa/dedup/model/OrgSimRel.java | 108 ++++++ .../oa/dedup/openorgs/oozie_app/workflow.xml | 35 +- .../oa/dedup/prepareNewOrgs_parameters.json | 62 ++++ .../oa/dedup/prepareOrgRels_parameters.json | 56 +++ .../dhp/oa/dedup/SparkOpenorgsTest.java | 296 +++++++++++++++ .../profiles/mock_orchestrator_openorgs.xml | 24 ++ .../raw/MigrateDbEntitiesApplication.java | 2 +- pom.xml | 3 - 19 files changed, 1436 insertions(+), 261 deletions(-) create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/OrgSimRel.java create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareNewOrgs_parameters.json create mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/profiles/mock_orchestrator_openorgs.xml diff --git a/dhp-workflows/dhp-dedup-openaire/pom.xml b/dhp-workflows/dhp-dedup-openaire/pom.xml index 03ddbcf4c..04e158542 100644 --- a/dhp-workflows/dhp-dedup-openaire/pom.xml +++ b/dhp-workflows/dhp-dedup-openaire/pom.xml @@ -90,7 +90,10 @@ com.fasterxml.jackson.core jackson-core - + + org.apache.httpcomponents + httpclient + diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java index 74cecb7b6..9a1127764 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java @@ -29,6 +29,7 @@ import eu.dnetlib.pace.config.DedupConfig; abstract class AbstractSparkAction implements Serializable { protected static final int NUM_PARTITIONS = 1000; + protected static final int NUM_CONNECTIONS = 20; protected static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupUtility.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupUtility.java index 01065510a..88873086d 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupUtility.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupUtility.java @@ -95,6 +95,11 @@ public class DedupUtility { return String.format("%s/%s/%s_simrel", basePath, actionSetId, entityType); } + public static String createOpenorgsMergeRelsPath( + final String basePath, final String actionSetId, final String entityType) { + return String.format("%s/%s/%s_openorgs_mergerels", basePath, actionSetId, entityType); + } + public static String createMergeRelPath( final String basePath, final String actionSetId, final String entityType) { return String.format("%s/%s/%s_mergerel", basePath, actionSetId, entityType); diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java index aa7a131e7..ff7aca627 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java @@ -6,6 +6,7 @@ import java.util.Optional; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaSparkContext; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoders; @@ -19,6 +20,7 @@ import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.schema.common.EntityType; import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.schema.oaf.OafEntity; +import eu.dnetlib.dhp.schema.oaf.Organization; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; @@ -34,7 +36,7 @@ public class SparkCopyOpenorgs extends AbstractSparkAction { ArgumentApplicationParser parser = new ArgumentApplicationParser( IOUtils .toString( - SparkCreateSimRels.class + SparkCopyOpenorgs.class .getResourceAsStream( "/eu/dnetlib/dhp/oa/dedup/copyOpenorgs_parameters.json"))); parser.parseArgument(args); @@ -72,7 +74,7 @@ public class SparkCopyOpenorgs extends AbstractSparkAction { final Class clazz = ModelSupport.entityTypes.get(EntityType.valueOf(subEntity)); - filterEntities(spark, entityPath, clazz) + filterOpenorgs(spark, entityPath) .write() .mode(SaveMode.Overwrite) .option("compression", "gzip") @@ -80,21 +82,20 @@ public class SparkCopyOpenorgs extends AbstractSparkAction { } - public static Dataset filterEntities( + public static Dataset filterOpenorgs( final SparkSession spark, - final String entitiesInputPath, - final Class clazz) { + final String entitiesInputPath) { - // - Dataset entities = spark - .read() - .textFile(entitiesInputPath) - .map( - (MapFunction) it -> { - T entity = OBJECT_MAPPER.readValue(it, clazz); - return entity; - }, - Encoders.kryo(clazz)); + JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); + Dataset entities = spark + .createDataset( + sc + .textFile(entitiesInputPath) + .map(it -> OBJECT_MAPPER.readValue(it, Organization.class)) + .rdd(), + Encoders.bean(Organization.class)); + + entities.show(); return entities.filter(entities.col("id").contains("openorgs____")); } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java index d705fca6b..4bb46222e 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java @@ -6,14 +6,13 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; -import eu.dnetlib.dhp.schema.oaf.KeyValue; -import eu.dnetlib.dhp.schema.oaf.Qualifier; -import eu.dnetlib.pace.config.DedupConfig; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.SaveMode; import org.apache.spark.sql.SparkSession; import org.dom4j.DocumentException; import org.slf4j.Logger; @@ -21,10 +20,13 @@ import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.schema.oaf.DataInfo; +import eu.dnetlib.dhp.schema.oaf.KeyValue; +import eu.dnetlib.dhp.schema.oaf.Qualifier; import eu.dnetlib.dhp.schema.oaf.Relation; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import eu.dnetlib.pace.config.DedupConfig; //copy simrels (verified) from relation to the workdir in order to make them available for the deduplication public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { @@ -83,17 +85,17 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { .textFile(relationPath) .map(patchRelFn(), Encoders.bean(Relation.class)) .toJavaRDD() - .filter(this::isOpenorgs) //takes only relations coming from openorgs - .filter(this::filterOpenorgsRels) //takes only isSimilarTo relations between organizations from openorgs - .filter(this::excludeOpenorgsMesh) //excludes relations between an organization and an openorgsmesh - .filter(this::excludeNonOpenorgs); //excludes relations with no openorgs id involved + .filter(this::isOpenorgs) // takes only relations coming from openorgs + .filter(this::filterOpenorgsRels) // takes only isSimilarTo relations between organizations from openorgs + .filter(this::excludeOpenorgsMesh) // excludes relations between an organization and an openorgsmesh + .filter(this::excludeNonOpenorgs); // excludes relations with no openorgs id involved - //turn openorgs isSimilarTo relations into mergerels - JavaRDD mergeRels = rawRels.flatMap(rel -> { + // turn openorgs isSimilarTo relations into mergerels + JavaRDD mergeRelsRDD = rawRels.flatMap(rel -> { List mergerels = new ArrayList<>(); - String openorgsId = rel.getSource().contains("openorgs____")? rel.getSource() : rel.getTarget(); - String mergedId = rel.getSource().contains("openorgs____")? rel.getTarget() : rel.getSource(); + String openorgsId = rel.getSource().contains("openorgs____") ? rel.getSource() : rel.getTarget(); + String mergedId = rel.getSource().contains("openorgs____") ? rel.getTarget() : rel.getSource(); mergerels.add(rel(openorgsId, mergedId, "merges", dedupConf)); mergerels.add(rel(mergedId, openorgsId, "isMergedIn", dedupConf)); @@ -101,7 +103,13 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { return mergerels.iterator(); }); - mergeRels.saveAsTextFile(outputPath); + spark + .createDataset( + mergeRelsRDD.rdd(), + Encoders.bean(Relation.class)) + .write() + .mode(SaveMode.Append) + .parquet(outputPath); } private static MapFunction patchRelFn() { @@ -116,7 +124,8 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { private boolean filterOpenorgsRels(Relation rel) { - if (rel.getRelClass().equals("isSimilarTo") && rel.getRelType().equals("organizationOrganization") && rel.getSubRelType().equals("dedup")) + if (rel.getRelClass().equals("isSimilarTo") && rel.getRelType().equals("organizationOrganization") + && rel.getSubRelType().equals("dedup")) return true; return false; } @@ -124,7 +133,7 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { private boolean isOpenorgs(Relation rel) { if (rel.getCollectedfrom() != null) { - for (KeyValue k: rel.getCollectedfrom()) { + for (KeyValue k : rel.getCollectedfrom()) { if (k.getValue().equals("OpenOrgs Database")) { return true; } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java index 3ce676f84..b7f88a5f6 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java @@ -6,13 +6,13 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; -import eu.dnetlib.dhp.schema.oaf.KeyValue; -import eu.dnetlib.dhp.schema.oaf.Qualifier; -import eu.dnetlib.pace.config.DedupConfig; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.graphx.Edge; +import org.apache.spark.rdd.RDD; +import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoders; import org.apache.spark.sql.SaveMode; import org.apache.spark.sql.SparkSession; @@ -22,97 +22,98 @@ import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.schema.oaf.DataInfo; +import eu.dnetlib.dhp.schema.oaf.KeyValue; +import eu.dnetlib.dhp.schema.oaf.Qualifier; import eu.dnetlib.dhp.schema.oaf.Relation; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import eu.dnetlib.pace.config.DedupConfig; //copy simrels (verified) from relation to the workdir in order to make them available for the deduplication public class SparkCopyOpenorgsSimRels extends AbstractSparkAction { - private static final Logger log = LoggerFactory.getLogger(SparkCopyOpenorgsMergeRels.class); + private static final Logger log = LoggerFactory.getLogger(SparkCopyOpenorgsSimRels.class); - public SparkCopyOpenorgsSimRels(ArgumentApplicationParser parser, SparkSession spark) { - super(parser, spark); - } + public SparkCopyOpenorgsSimRels(ArgumentApplicationParser parser, SparkSession spark) { + super(parser, spark); + } - public static void main(String[] args) throws Exception { - ArgumentApplicationParser parser = new ArgumentApplicationParser( - IOUtils - .toString( - SparkCopyOpenorgsSimRels.class - .getResourceAsStream( - "/eu/dnetlib/dhp/oa/dedup/copyOpenorgsMergeRels_parameters.json"))); - parser.parseArgument(args); + public static void main(String[] args) throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCopyOpenorgsSimRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/copyOpenorgsMergeRels_parameters.json"))); + parser.parseArgument(args); - SparkConf conf = new SparkConf(); - new SparkCopyOpenorgsSimRels(parser, getSparkSession(conf)) - .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); - } + SparkConf conf = new SparkConf(); + new SparkCopyOpenorgsSimRels(parser, getSparkSession(conf)) + .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); + } - @Override - public void run(ISLookUpService isLookUpService) - throws DocumentException, IOException, ISLookUpException { + @Override + public void run(ISLookUpService isLookUpService) + throws DocumentException, IOException, ISLookUpException { - // read oozie parameters - final String graphBasePath = parser.get("graphBasePath"); - final String actionSetId = parser.get("actionSetId"); - final String workingPath = parser.get("workingPath"); - final int numPartitions = Optional - .ofNullable(parser.get("numPartitions")) - .map(Integer::valueOf) - .orElse(NUM_PARTITIONS); + // read oozie parameters + final String graphBasePath = parser.get("graphBasePath"); + final String actionSetId = parser.get("actionSetId"); + final String workingPath = parser.get("workingPath"); + final int numPartitions = Optional + .ofNullable(parser.get("numPartitions")) + .map(Integer::valueOf) + .orElse(NUM_PARTITIONS); - log.info("numPartitions: '{}'", numPartitions); - log.info("graphBasePath: '{}'", graphBasePath); - log.info("actionSetId: '{}'", actionSetId); - log.info("workingPath: '{}'", workingPath); + log.info("numPartitions: '{}'", numPartitions); + log.info("graphBasePath: '{}'", graphBasePath); + log.info("actionSetId: '{}'", actionSetId); + log.info("workingPath: '{}'", workingPath); - log.info("Copying OpenOrgs SimRels"); + log.info("Copying OpenOrgs SimRels"); - final String outputPath = DedupUtility.createSimRelPath(workingPath, actionSetId, "organization"); + final String outputPath = DedupUtility.createSimRelPath(workingPath, actionSetId, "organization"); - removeOutputDir(spark, outputPath); + final String relationPath = DedupUtility.createEntityPath(graphBasePath, "relation"); - final String relationPath = DedupUtility.createEntityPath(graphBasePath, "relation"); + Dataset rawRels = spark + .read() + .textFile(relationPath) + .map(patchRelFn(), Encoders.bean(Relation.class)) + .filter(this::filterOpenorgsRels); - JavaRDD rawRels = spark - .read() - .textFile(relationPath) - .map(patchRelFn(), Encoders.bean(Relation.class)) - .toJavaRDD() - .filter(this::isOpenorgs) - .filter(this::filterOpenorgsRels); + save(rawRels, outputPath, SaveMode.Append); - save(spark.createDataset(rawRels.rdd(),Encoders.bean(Relation.class)), outputPath, SaveMode.Append); - } + log.info("Copied " + rawRels.count() + " Similarity Relations"); + } - private static MapFunction patchRelFn() { - return value -> { - final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); - if (rel.getDataInfo() == null) { - rel.setDataInfo(new DataInfo()); - } - return rel; - }; - } + private static MapFunction patchRelFn() { + return value -> { + final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); + if (rel.getDataInfo() == null) { + rel.setDataInfo(new DataInfo()); + } + return rel; + }; + } - private boolean filterOpenorgsRels(Relation rel) { + private boolean filterOpenorgsRels(Relation rel) { - if (rel.getRelClass().equals("isSimilarTo") && rel.getRelType().equals("organizationOrganization") && rel.getSubRelType().equals("dedup")) - return true; - return false; - } + if (rel.getRelClass().equals("isSimilarTo") && rel.getRelType().equals("organizationOrganization") + && rel.getSubRelType().equals("dedup") && isOpenorgs(rel)) + return true; + return false; + } - private boolean isOpenorgs(Relation rel) { + private boolean isOpenorgs(Relation rel) { - if (rel.getCollectedfrom() != null) { - for (KeyValue k: rel.getCollectedfrom()) { - if (k.getValue().equals("OpenOrgs Database")) { - return true; - } - } - } - return false; - } + if (rel.getCollectedfrom() != null) { + for (KeyValue k : rel.getCollectedfrom()) { + if (k.getValue() != null && k.getValue().equals("OpenOrgs Database")) { + return true; + } + } + } + return false; + } } - diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRelationsNoOpenorgs.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRelationsNoOpenorgs.java index 319c40d8d..64a110892 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRelationsNoOpenorgs.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRelationsNoOpenorgs.java @@ -1,12 +1,9 @@ + package eu.dnetlib.dhp.oa.dedup; -import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.schema.common.ModelSupport; -import eu.dnetlib.dhp.schema.oaf.*; -import eu.dnetlib.dhp.utils.ISLookupClientFactory; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import eu.dnetlib.pace.util.MapDocumentUtil; +import java.io.IOException; +import java.util.Optional; + import org.apache.commons.io.IOUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileStatus; @@ -19,92 +16,95 @@ import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.api.java.function.PairFunction; +import org.apache.spark.sql.*; import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Encoders; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.SparkSession; import org.dom4j.DocumentException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import scala.Tuple2; -import java.io.IOException; -import java.util.Optional; +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.common.ModelSupport; +import eu.dnetlib.dhp.schema.oaf.*; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import eu.dnetlib.pace.util.MapDocumentUtil; +import scala.Tuple2; public class SparkCopyRelationsNoOpenorgs extends AbstractSparkAction { - private static final Logger log = LoggerFactory.getLogger(SparkUpdateEntity.class); + private static final Logger log = LoggerFactory.getLogger(SparkUpdateEntity.class); - private static final String IDJSONPATH = "$.id"; + public SparkCopyRelationsNoOpenorgs(ArgumentApplicationParser parser, SparkSession spark) { + super(parser, spark); + } - public SparkCopyRelationsNoOpenorgs(ArgumentApplicationParser parser, SparkSession spark) { - super(parser, spark); - } + public static void main(String[] args) throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCopyRelationsNoOpenorgs.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/updateEntity_parameters.json"))); + parser.parseArgument(args); - public static void main(String[] args) throws Exception { - ArgumentApplicationParser parser = new ArgumentApplicationParser( - IOUtils - .toString( - SparkCopyRelationsNoOpenorgs.class - .getResourceAsStream( - "/eu/dnetlib/dhp/oa/dedup/updateEntity_parameters.json"))); - parser.parseArgument(args); + SparkConf conf = new SparkConf(); + conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer"); + conf.registerKryoClasses(ModelSupport.getOafModelClasses()); - SparkConf conf = new SparkConf(); - conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer"); - conf.registerKryoClasses(ModelSupport.getOafModelClasses()); + new SparkUpdateEntity(parser, getSparkSession(conf)) + .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); + } - new SparkUpdateEntity(parser, getSparkSession(conf)) - .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); - } + public void run(ISLookUpService isLookUpService) throws IOException { - public void run(ISLookUpService isLookUpService) throws IOException { + final String graphBasePath = parser.get("graphBasePath"); + final String workingPath = parser.get("workingPath"); + final String dedupGraphPath = parser.get("dedupGraphPath"); - final String graphBasePath = parser.get("graphBasePath"); - final String workingPath = parser.get("workingPath"); - final String dedupGraphPath = parser.get("dedupGraphPath"); + log.info("graphBasePath: '{}'", graphBasePath); + log.info("workingPath: '{}'", workingPath); + log.info("dedupGraphPath: '{}'", dedupGraphPath); - log.info("graphBasePath: '{}'", graphBasePath); - log.info("workingPath: '{}'", workingPath); - log.info("dedupGraphPath: '{}'", dedupGraphPath); + final String relationPath = DedupUtility.createEntityPath(graphBasePath, "relation"); + final String outputPath = DedupUtility.createEntityPath(dedupGraphPath, "relation"); - final JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); + removeOutputDir(spark, outputPath); - final String relationPath = DedupUtility.createEntityPath(graphBasePath, "relation"); - final String outputPath = DedupUtility.createEntityPath(dedupGraphPath, "relation"); + JavaRDD simRels = spark + .read() + .textFile(relationPath) + .map(patchRelFn(), Encoders.bean(Relation.class)) + .toJavaRDD() + .filter(this::excludeOpenorgsRels); - removeOutputDir(spark, outputPath); + spark + .createDataset(simRels.rdd(), Encoders.bean(Relation.class)) + .write() + .mode(SaveMode.Overwrite) + .json(outputPath); - JavaRDD simRels = spark - .read() - .textFile(relationPath) - .map(patchRelFn(), Encoders.bean(Relation.class)) - .toJavaRDD() - .filter(this::excludeOpenorgsRels); + } - simRels.saveAsTextFile(outputPath); + private static MapFunction patchRelFn() { + return value -> { + final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); + if (rel.getDataInfo() == null) { + rel.setDataInfo(new DataInfo()); + } + return rel; + }; + } - } + private boolean excludeOpenorgsRels(Relation rel) { - private static MapFunction patchRelFn() { - return value -> { - final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); - if (rel.getDataInfo() == null) { - rel.setDataInfo(new DataInfo()); - } - return rel; - }; - } - - private boolean excludeOpenorgsRels(Relation rel) { - - if (rel.getCollectedfrom() != null) { - for (KeyValue k: rel.getCollectedfrom()) { - if (k.getValue().equals("OpenOrgs Database")) { - return false; - } - } - } - return true; - } + if (rel.getCollectedfrom() != null) { + for (KeyValue k : rel.getCollectedfrom()) { + if (k.getValue().equals("OpenOrgs Database")) { + return false; + } + } + } + return true; + } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateSimRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateSimRels.java index b3ee47bfc..a7566f2e2 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateSimRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateSimRels.java @@ -10,6 +10,7 @@ import org.apache.spark.api.java.JavaPairRDD; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; import org.apache.spark.api.java.function.PairFunction; +import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoders; import org.apache.spark.sql.SaveMode; import org.apache.spark.sql.SparkSession; @@ -81,7 +82,6 @@ public class SparkCreateSimRels extends AbstractSparkAction { log.info("Creating simrels for: '{}'", subEntity); final String outputPath = DedupUtility.createSimRelPath(workingPath, actionSetId, subEntity); - removeOutputDir(spark, outputPath); JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); @@ -99,13 +99,19 @@ public class SparkCreateSimRels extends AbstractSparkAction { .createSortedBlocks(mapDocuments, dedupConf) .repartition(numPartitions); - // create relations by comparing only elements in the same group - Deduper - .computeRelations(sc, blocks, dedupConf) - .map(t -> createSimRel(t._1(), t._2(), entity)) - .repartition(numPartitions) - .map(r -> OBJECT_MAPPER.writeValueAsString(r)) - .saveAsTextFile(outputPath); + Dataset simRels = spark + .createDataset( + Deduper + .computeRelations(sc, blocks, dedupConf) + .map(t -> createSimRel(t._1(), t._2(), entity)) + .repartition(numPartitions) + .rdd(), + Encoders.bean(Relation.class)); + + save(simRels, outputPath, SaveMode.Append); + + log.info("Generated " + simRels.count() + " Similarity Relations"); + } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java new file mode 100644 index 000000000..3b29e1e17 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java @@ -0,0 +1,249 @@ + +package eu.dnetlib.dhp.oa.dedup; + +import java.io.IOException; +import java.util.Optional; +import java.util.Properties; + +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaPairRDD; +import org.apache.spark.api.java.JavaRDD; +import org.apache.spark.api.java.function.FilterFunction; +import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.SaveMode; +import org.apache.spark.sql.SparkSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.oa.dedup.model.OrgSimRel; +import eu.dnetlib.dhp.schema.common.ModelSupport; +import eu.dnetlib.dhp.schema.oaf.DataInfo; +import eu.dnetlib.dhp.schema.oaf.Organization; +import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import scala.Tuple2; + +public class SparkPrepareNewOrgs extends AbstractSparkAction { + + private static final Logger log = LoggerFactory.getLogger(SparkPrepareNewOrgs.class); + + public SparkPrepareNewOrgs(ArgumentApplicationParser parser, SparkSession spark) { + super(parser, spark); + } + + public static void main(String[] args) throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkPrepareNewOrgs.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/prepareNewOrgs_parameters.json"))); + parser.parseArgument(args); + + SparkConf conf = new SparkConf(); + conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer"); + conf.registerKryoClasses(ModelSupport.getOafModelClasses()); + + new SparkPrepareNewOrgs(parser, getSparkSession(conf)) + .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); + } + + @Override + public void run(ISLookUpService isLookUpService) throws IOException { + + final String graphBasePath = parser.get("graphBasePath"); + final String isLookUpUrl = parser.get("isLookUpUrl"); + final String actionSetId = parser.get("actionSetId"); + final String workingPath = parser.get("workingPath"); + final int numConnections = Optional + .ofNullable(parser.get("numConnections")) + .map(Integer::valueOf) + .orElse(NUM_CONNECTIONS); + + final String apiUrl = Optional + .ofNullable(parser.get("apiUrl")) + .orElse(""); + + final String dbUrl = parser.get("dbUrl"); + final String dbTable = parser.get("dbTable"); + final String dbUser = parser.get("dbUser"); + final String dbPwd = parser.get("dbPwd"); + + log.info("graphBasePath: '{}'", graphBasePath); + log.info("isLookUpUrl: '{}'", isLookUpUrl); + log.info("actionSetId: '{}'", actionSetId); + log.info("workingPath: '{}'", workingPath); + log.info("numPartitions: '{}'", numConnections); + log.info("apiUrl: '{}'", apiUrl); + log.info("dbUrl: '{}'", dbUrl); + log.info("dbUser: '{}'", dbUser); + log.info("table: '{}'", dbTable); + log.info("dbPwd: '{}'", "xxx"); + + final String entityPath = DedupUtility.createEntityPath(graphBasePath, "organization"); + final String mergeRelPath = DedupUtility.createMergeRelPath(workingPath, actionSetId, "organization"); + final String relationPath = DedupUtility.createEntityPath(graphBasePath, "relation"); + + Dataset newOrgs = createNewOrgs(spark, mergeRelPath, relationPath, entityPath); + + final Properties connectionProperties = new Properties(); + connectionProperties.put("user", dbUser); + connectionProperties.put("password", dbPwd); + + log.info("Number of New Organization created: '{}'", newOrgs.count()); + + newOrgs + .repartition(numConnections) + .write() + .mode(SaveMode.Append) + .jdbc(dbUrl, dbTable, connectionProperties); + + if (!apiUrl.isEmpty()) + updateSimRels(apiUrl); + + } + + public static Dataset createNewOrgs( + final SparkSession spark, + final String mergeRelsPath, + final String relationPath, + final String entitiesPath) { + + // collect diffrels from the raw graph relations: + JavaPairRDD diffRels = spark + .read() + .textFile(relationPath) + .map(patchRelFn(), Encoders.bean(Relation.class)) + .toJavaRDD() + .filter(r -> filterRels(r, "organization")) + // take the worst id of the diffrel: + .mapToPair(rel -> { + if (compareIds(rel.getSource(), rel.getTarget()) > 0) + return new Tuple2<>(rel.getSource(), "diffRel"); + else + return new Tuple2<>(rel.getTarget(), "diffRel"); + }) + .distinct(); + log.info("Number of DiffRels collected: '{}'", diffRels.count()); + + // collect entities: + Dataset> entities = spark + .read() + .textFile(entitiesPath) + .map( + (MapFunction>) it -> { + Organization entity = OBJECT_MAPPER.readValue(it, Organization.class); + return new Tuple2<>(entity.getId(), entity); + }, + Encoders.tuple(Encoders.STRING(), Encoders.kryo(Organization.class))); + + // collect mergerels and remove ids in the diffrels + Dataset> openorgsRels = spark + .createDataset( + spark + .read() + .load(mergeRelsPath) + .as(Encoders.bean(Relation.class)) + .where("relClass == 'isMergedIn'") + .toJavaRDD() + .mapToPair(r -> new Tuple2<>(r.getSource(), r.getTarget())) // + .leftOuterJoin(diffRels) // + .filter(rel -> !rel._2()._2().isPresent()) + .mapToPair(rel -> new Tuple2<>(rel._1(), rel._2()._1())) + .rdd(), + Encoders.tuple(Encoders.STRING(), Encoders.STRING())); + log.info("Number of Openorgs Relations loaded: '{}'", openorgsRels.count()); + + return entities + .joinWith(openorgsRels, entities.col("_1").equalTo(openorgsRels.col("_1")), "left") + .filter((FilterFunction, Tuple2>>) t -> t._2() == null) + // take entities not in mergerels (they are single entities, therefore are new orgs) + .filter( + (FilterFunction, Tuple2>>) t -> !t + ._1() + ._1() + .contains("openorgs")) + // exclude openorgs, don't need to propose them as new orgs + .map( + (MapFunction, Tuple2>, OrgSimRel>) r -> new OrgSimRel( + "", + r._1()._2().getOriginalId().get(0), + r._1()._2().getLegalname() != null ? r._1()._2().getLegalname().getValue() : "", + r._1()._2().getLegalshortname() != null ? r._1()._2().getLegalshortname().getValue() : "", + r._1()._2().getCountry() != null ? r._1()._2().getCountry().getClassid() : "", + r._1()._2().getWebsiteurl() != null ? r._1()._2().getWebsiteurl().getValue() : "", + r._1()._2().getCollectedfrom().get(0).getValue(), ""), + Encoders.bean(OrgSimRel.class)); + + } + + private static String updateSimRels(final String apiUrl) throws IOException { + + log.info("Updating simrels on the portal"); + + final HttpGet req = new HttpGet(apiUrl); + try (final CloseableHttpClient client = HttpClients.createDefault()) { + try (final CloseableHttpResponse response = client.execute(req)) { + return IOUtils.toString(response.getEntity().getContent()); + } + } + } + + private static MapFunction patchRelFn() { + return value -> { + final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); + if (rel.getDataInfo() == null) { + rel.setDataInfo(new DataInfo()); + } + return rel; + }; + } + + public static int compareIds(String o1, String o2) { + if (o1.contains("openorgs____") && o2.contains("openorgs____")) + return o1.compareTo(o2); + if (o1.contains("corda") && o2.contains("corda")) + return o1.compareTo(o2); + + if (o1.contains("openorgs____")) + return -1; + if (o2.contains("openorgs____")) + return 1; + + if (o1.contains("corda")) + return -1; + if (o2.contains("corda")) + return 1; + + return o1.compareTo(o2); + } + + private static boolean filterRels(Relation rel, String entityType) { + + switch (entityType) { + case "result": + if (rel.getRelClass().equals("isDifferentFrom") && rel.getRelType().equals("resultResult") + && rel.getSubRelType().equals("dedup")) + return true; + break; + case "organization": + if (rel.getRelClass().equals("isDifferentFrom") && rel.getRelType().equals("organizationOrganization") + && rel.getSubRelType().equals("dedup")) + return true; + break; + default: + return false; + } + return false; + } + +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java new file mode 100644 index 000000000..cbca0b326 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java @@ -0,0 +1,341 @@ + +package eu.dnetlib.dhp.oa.dedup; + +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import org.apache.commons.io.IOUtils; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaRDD; +import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.SaveMode; +import org.apache.spark.sql.SparkSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Lists; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.oa.dedup.model.OrgSimRel; +import eu.dnetlib.dhp.schema.common.ModelSupport; +import eu.dnetlib.dhp.schema.oaf.DataInfo; +import eu.dnetlib.dhp.schema.oaf.Organization; +import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import scala.Tuple2; +import scala.Tuple3; + +public class SparkPrepareOrgRels extends AbstractSparkAction { + + private static final Logger log = LoggerFactory.getLogger(SparkPrepareOrgRels.class); + + public SparkPrepareOrgRels(ArgumentApplicationParser parser, SparkSession spark) { + super(parser, spark); + } + + public static void main(String[] args) throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCreateSimRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json"))); + parser.parseArgument(args); + + SparkConf conf = new SparkConf(); + conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer"); + conf.registerKryoClasses(ModelSupport.getOafModelClasses()); + + new SparkPrepareOrgRels(parser, getSparkSession(conf)) + .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); + } + + @Override + public void run(ISLookUpService isLookUpService) throws IOException { + + final String graphBasePath = parser.get("graphBasePath"); + final String isLookUpUrl = parser.get("isLookUpUrl"); + final String actionSetId = parser.get("actionSetId"); + final String workingPath = parser.get("workingPath"); + final int numConnections = Optional + .ofNullable(parser.get("numConnections")) + .map(Integer::valueOf) + .orElse(NUM_CONNECTIONS); + + final String dbUrl = parser.get("dbUrl"); + final String dbTable = parser.get("dbTable"); + final String dbUser = parser.get("dbUser"); + final String dbPwd = parser.get("dbPwd"); + + log.info("graphBasePath: '{}'", graphBasePath); + log.info("isLookUpUrl: '{}'", isLookUpUrl); + log.info("actionSetId: '{}'", actionSetId); + log.info("workingPath: '{}'", workingPath); + log.info("numPartitions: '{}'", numConnections); + log.info("dbUrl: '{}'", dbUrl); + log.info("dbUser: '{}'", dbUser); + log.info("table: '{}'", dbTable); + log.info("dbPwd: '{}'", "xxx"); + + final String mergeRelPath = DedupUtility.createMergeRelPath(workingPath, actionSetId, "organization"); + final String entityPath = DedupUtility.createEntityPath(graphBasePath, "organization"); + final String relationPath = DedupUtility.createEntityPath(graphBasePath, "relation"); + + Dataset relations = createRelations(spark, mergeRelPath, relationPath, entityPath); + + final Properties connectionProperties = new Properties(); + connectionProperties.put("user", dbUser); + connectionProperties.put("password", dbPwd); + + relations + .repartition(numConnections) + .write() + .mode(SaveMode.Overwrite) + .jdbc(dbUrl, dbTable, connectionProperties); + + } + + private static boolean filterRels(Relation rel, String entityType) { + + switch (entityType) { + case "result": + if (rel.getRelClass().equals("isDifferentFrom") && rel.getRelType().equals("resultResult") + && rel.getSubRelType().equals("dedup")) + return true; + break; + case "organization": + if (rel.getRelClass().equals("isDifferentFrom") && rel.getRelType().equals("organizationOrganization") + && rel.getSubRelType().equals("dedup")) + return true; + break; + default: + return false; + } + return false; + } + + // create openorgs simrels starting from mergerels, remove the diffrels + public static Dataset createRelations( + final SparkSession spark, + final String mergeRelsPath, + final String relationPath, + final String entitiesPath) { + + // collect diffrels from the raw graph relations: <, "diffRel"> + JavaRDD, String>> diffRels = spark + .read() + .textFile(relationPath) + .map(patchRelFn(), Encoders.bean(Relation.class)) + .toJavaRDD() + .filter(r -> filterRels(r, "organization")) + // put the best id as source of the diffrel: + .map(rel -> { + if (compareIds(rel.getSource(), rel.getTarget()) < 0) + return new Tuple2<>(new Tuple2<>(rel.getSource(), rel.getTarget()), "diffRel"); + else + return new Tuple2<>(new Tuple2<>(rel.getTarget(), rel.getSource()), "diffRel"); + }) + .distinct(); + log.info("Number of DiffRels collected: {}", diffRels.count()); + + // collect all the organizations + Dataset> entities = spark + .read() + .textFile(entitiesPath) + .map( + (MapFunction>) it -> { + Organization entity = OBJECT_MAPPER.readValue(it, Organization.class); + return new Tuple2<>(entity.getId(), entity); + }, + Encoders.tuple(Encoders.STRING(), Encoders.kryo(Organization.class))); + + // relations with their group (connected component id) + JavaRDD, String>> rawOpenorgsRels = spark + .read() + .load(mergeRelsPath) + .as(Encoders.bean(Relation.class)) + .where("relClass == 'merges'") + .toJavaRDD() + .mapToPair(r -> new Tuple2<>(r.getSource(), r.getTarget())) + .filter(t -> !t._2().contains("openorgsmesh")) // remove openorgsmesh: they are only for dedup + .groupByKey() + .map(g -> Lists.newArrayList(g._2())) + .filter(l -> l.size() > 1) + .flatMap(l -> { + String groupId = "group::" + UUID.randomUUID(); + List ids = sortIds(l); // sort IDs by type + List, String>> rels = new ArrayList<>(); + String source = ids.get(0); + for (String target : ids) { + rels.add(new Tuple2<>(new Tuple2<>(source, target), groupId)); + } + + return rels.iterator(); + }); + log.info("Number of Raw Openorgs Relations created: {}", rawOpenorgsRels.count()); + + // filter out diffRels + JavaRDD> openorgsRels = rawOpenorgsRels + .union(diffRels) + // concatenation of source and target: or + .mapToPair(t -> new Tuple2<>(t._1()._1() + "@@@" + t._1()._2(), t._2())) + .groupByKey() + .map( + g -> new Tuple2<>(g._1(), StreamSupport + .stream(g._2().spliterator(), false) + .collect(Collectors.toList()))) + // : take only relations with only the group_id, it + // means they are correct. If the diffRel is present the relation has to be removed + .filter(g -> g._2().size() == 1 && g._2().get(0).contains("group::")) + .map( + t -> new Tuple3<>( + t._1().split("@@@")[0], + t._1().split("@@@")[1], + t._2().get(0))); + log.info("Number of Openorgs Relations created: '{}'", openorgsRels.count()); + + // + Dataset> relations = spark + .createDataset( + openorgsRels.rdd(), + Encoders.tuple(Encoders.STRING(), Encoders.STRING(), Encoders.STRING())); + + // create orgsimrels + Dataset> relations2 = relations + .joinWith(entities, relations.col("_2").equalTo(entities.col("_1")), "inner") + .map( + (MapFunction, Tuple2>, OrgSimRel>) r -> new OrgSimRel( + r._1()._1(), + r._2()._2().getOriginalId().get(0), + r._2()._2().getLegalname() != null ? r._2()._2().getLegalname().getValue() : "", + r._2()._2().getLegalshortname() != null ? r._2()._2().getLegalshortname().getValue() : "", + r._2()._2().getCountry() != null ? r._2()._2().getCountry().getClassid() : "", + r._2()._2().getWebsiteurl() != null ? r._2()._2().getWebsiteurl().getValue() : "", + r._2()._2().getCollectedfrom().get(0).getValue(), + r._1()._3()), + Encoders.bean(OrgSimRel.class)) + .map( + (MapFunction>) o -> new Tuple2<>(o.getLocal_id(), o), + Encoders.tuple(Encoders.STRING(), Encoders.bean(OrgSimRel.class))); + + return relations2 + .joinWith(entities, relations2.col("_1").equalTo(entities.col("_1")), "inner") + .map( + (MapFunction, Tuple2>, OrgSimRel>) r -> { + OrgSimRel orgSimRel = r._1()._2(); + orgSimRel.setLocal_id(r._2()._2().getOriginalId().get(0)); + return orgSimRel; + }, + Encoders.bean(OrgSimRel.class)); + + } + + public static int compareIds(String o1, String o2) { + if (o1.contains("openorgs____") && o2.contains("openorgs____")) + return o1.compareTo(o2); + if (o1.contains("corda") && o2.contains("corda")) + return o1.compareTo(o2); + + if (o1.contains("openorgs____")) + return -1; + if (o2.contains("openorgs____")) + return 1; + + if (o1.contains("corda")) + return -1; + if (o2.contains("corda")) + return 1; + + return o1.compareTo(o2); + } + + // Sort IDs basing on the type. Priority: 1) openorgs, 2)corda, 3)alphabetic + public static List sortIds(List ids) { + ids.sort((o1, o2) -> compareIds(o1, o2)); + return ids; + } + + public static Dataset createRelationsFromScratch( + final SparkSession spark, + final String mergeRelsPath, + final String entitiesPath) { + + // + Dataset> entities = spark + .read() + .textFile(entitiesPath) + .map( + (MapFunction>) it -> { + Organization entity = OBJECT_MAPPER.readValue(it, Organization.class); + return new Tuple2<>(entity.getId(), entity); + }, + Encoders.tuple(Encoders.STRING(), Encoders.kryo(Organization.class))); + + Dataset> relations = spark + .createDataset( + spark + .read() + .load(mergeRelsPath) + .as(Encoders.bean(Relation.class)) + .where("relClass == 'merges'") + .toJavaRDD() + .mapToPair(r -> new Tuple2<>(r.getSource(), r.getTarget())) + .groupByKey() + .flatMap(g -> { + List> rels = new ArrayList<>(); + for (String id1 : g._2()) { + for (String id2 : g._2()) { + if (!id1.equals(id2)) + if (id1.contains("openorgs____") && !id2.contains("openorgsmesh")) + rels.add(new Tuple2<>(id1, id2)); + } + } + return rels.iterator(); + }) + .rdd(), + Encoders.tuple(Encoders.STRING(), Encoders.STRING())); + + Dataset> relations2 = relations // + .joinWith(entities, relations.col("_2").equalTo(entities.col("_1")), "inner") + .map( + (MapFunction, Tuple2>, OrgSimRel>) r -> new OrgSimRel( + r._1()._1(), + r._2()._2().getOriginalId().get(0), + r._2()._2().getLegalname() != null ? r._2()._2().getLegalname().getValue() : "", + r._2()._2().getLegalshortname() != null ? r._2()._2().getLegalshortname().getValue() : "", + r._2()._2().getCountry() != null ? r._2()._2().getCountry().getClassid() : "", + r._2()._2().getWebsiteurl() != null ? r._2()._2().getWebsiteurl().getValue() : "", + r._2()._2().getCollectedfrom().get(0).getValue(), + "group::" + r._1()._1()), + Encoders.bean(OrgSimRel.class)) + .map( + (MapFunction>) o -> new Tuple2<>(o.getLocal_id(), o), + Encoders.tuple(Encoders.STRING(), Encoders.bean(OrgSimRel.class))); + + return relations2 + .joinWith(entities, relations2.col("_1").equalTo(entities.col("_1")), "inner") + .map( + (MapFunction, Tuple2>, OrgSimRel>) r -> { + OrgSimRel orgSimRel = r._1()._2(); + orgSimRel.setLocal_id(r._2()._2().getOriginalId().get(0)); + return orgSimRel; + }, + Encoders.bean(OrgSimRel.class)); + + } + + private static MapFunction patchRelFn() { + return value -> { + final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); + if (rel.getDataInfo() == null) { + rel.setDataInfo(new DataInfo()); + } + return rel; + }; + } +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkRemoveDiffRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkRemoveDiffRels.java index 6f012e00a..4c0bfadf0 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkRemoveDiffRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkRemoveDiffRels.java @@ -27,6 +27,8 @@ import org.dom4j.DocumentException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.Iterables; + import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.oa.dedup.graph.ConnectedComponent; import eu.dnetlib.dhp.oa.dedup.graph.GraphProcessor; @@ -95,6 +97,9 @@ public class SparkRemoveDiffRels extends AbstractSparkAction { final String relationPath = DedupUtility.createEntityPath(graphBasePath, subEntity); + final String openorgsMergeRelsPath = DedupUtility + .createOpenorgsMergeRelsPath(workingPath, actionSetId, subEntity); + final int maxIterations = dedupConf.getWf().getMaxIterations(); log.info("Max iterations {}", maxIterations); @@ -105,67 +110,103 @@ public class SparkRemoveDiffRels extends AbstractSparkAction { .where("relClass == 'merges'") .toJavaRDD(); + System.out.println("mergeRelsRDD = " + mergeRelsRDD.count()); + +// JavaRDD, String>> diffRelsRDD = spark +// .read() +// .textFile(relationPath) +// .map(patchRelFn(), Encoders.bean(Relation.class)) +// .toJavaRDD() +// .filter(r -> filterRels(r, entity)) +// .map(rel -> { +// if (rel.getSource().compareTo(rel.getTarget()) < 0) +// return new Tuple2<>(new Tuple2<>(rel.getSource(), rel.getTarget()), "diffRel"); +// else +// return new Tuple2<>(new Tuple2<>(rel.getTarget(), rel.getSource()), "diffRel"); +// }); + // THIS IS FOR TESTING PURPOSE JavaRDD, String>> diffRelsRDD = spark .read() - .textFile(relationPath) - .map(patchRelFn(), Encoders.bean(Relation.class)) + .load(mergeRelsPath) + .as(Encoders.bean(Relation.class)) .toJavaRDD() - .filter(r -> filterRels(r, entity)) .map(rel -> { if (rel.getSource().compareTo(rel.getTarget()) < 0) return new Tuple2<>(new Tuple2<>(rel.getSource(), rel.getTarget()), "diffRel"); else return new Tuple2<>(new Tuple2<>(rel.getTarget(), rel.getSource()), "diffRel"); + }) + .distinct(); + + System.out.println("diffRelsRDD = " + diffRelsRDD.count()); + +// JavaRDD, String>> flatMergeRels = mergeRelsRDD +// .mapToPair(rel -> new Tuple2<>(rel.getSource(), rel.getTarget())) +// .groupByKey() +// .flatMap(g -> { +// List, String>> rels = new ArrayList<>(); +// +// List ids = StreamSupport +// .stream(g._2().spliterator(), false) +// .collect(Collectors.toList()); +// +// for (int i = 0; i < ids.size(); i++) { +// for (int j = i + 1; j < ids.size(); j++) { +// if (ids.get(i).compareTo(ids.get(j)) < 0) +// rels.add(new Tuple2<>(new Tuple2<>(ids.get(i), ids.get(j)), g._1())); +// else +// rels.add(new Tuple2<>(new Tuple2<>(ids.get(j), ids.get(i)), g._1())); +// } +// } +// return rels.iterator(); +// +// }); + JavaRDD, String>> mergeRels = mergeRelsRDD + .map(rel -> { + if (rel.getSource().compareTo(rel.getTarget()) < 0) + return new Tuple2<>(new Tuple2<>(rel.getSource(), rel.getTarget()), "mergeRel"); + else + return new Tuple2<>(new Tuple2<>(rel.getTarget(), rel.getSource()), "mergeRel"); }); + System.out.println("mergeRelsProcessed = " + mergeRels.count()); - JavaRDD, String>> flatMergeRels = mergeRelsRDD - .mapToPair(rel -> new Tuple2<>(rel.getSource(), rel.getTarget())) - .groupByKey() - .flatMap(g -> { - List, String>> rels = new ArrayList<>(); - - List ids = StreamSupport - .stream(g._2().spliterator(), false) - .collect(Collectors.toList()); - - for (int i = 0; i < ids.size(); i++) { - for (int j = i + 1; j < ids.size(); j++) { - if (ids.get(i).compareTo(ids.get(j)) < 0) - rels.add(new Tuple2<>(new Tuple2<>(ids.get(i), ids.get(j)), g._1())); - else - rels.add(new Tuple2<>(new Tuple2<>(ids.get(j), ids.get(i)), g._1())); - } - } - return rels.iterator(); - - }); - - JavaRDD purgedMergeRels = flatMergeRels +// JavaRDD purgedMergeRels = flatMergeRels +// .union(diffRelsRDD) +// .mapToPair(rel -> new Tuple2<>(rel._1(), Arrays.asList(rel._2()))) +// .reduceByKey((a, b) -> { +// List list = new ArrayList(); +// list.addAll(a); +// list.addAll(b); +// return list; +// }) +// .filter(rel -> rel._2().size() == 1) +// .mapToPair(rel -> new Tuple2<>(rel._2().get(0), rel._1())) +// .flatMap(rel -> { +// List> rels = new ArrayList<>(); +// String source = rel._1(); +// rels.add(new Tuple2<>(source, rel._2()._1())); +// rels.add(new Tuple2<>(source, rel._2()._2())); +// return rels.iterator(); +// }) +// .distinct() +// .flatMap(rel -> tupleToMergeRel(rel, dedupConf)); + JavaRDD purgedMergeRels = mergeRels .union(diffRelsRDD) - .mapToPair(rel -> new Tuple2<>(rel._1(), Arrays.asList(rel._2()))) - .reduceByKey((a, b) -> { - List list = new ArrayList(); - list.addAll(a); - list.addAll(b); - return list; - }) - .filter(rel -> rel._2().size() == 1) - .mapToPair(rel -> new Tuple2<>(rel._2().get(0), rel._1())) - .flatMap(rel -> { - List> rels = new ArrayList<>(); - String source = rel._1(); - rels.add(new Tuple2<>(source, rel._2()._1())); - rels.add(new Tuple2<>(source, rel._2()._2())); - return rels.iterator(); - }) - .distinct() - .flatMap(rel -> tupleToMergeRel(rel, dedupConf)); + .mapToPair(t -> new Tuple2<>(t._1()._1() + "|||" + t._1()._2(), t._2())) + .groupByKey() + .filter(g -> Iterables.size(g._2()) == 1) + .flatMap( + t -> tupleToMergeRel( + new Tuple2<>(t._1().split("\\|\\|\\|")[0], t._1().split("\\|\\|\\|")[1]), + dedupConf)); + + System.out.println("purgedMergeRels = " + purgedMergeRels.count()); spark .createDataset(purgedMergeRels.rdd(), Encoders.bean(Relation.class)) .write() .mode(SaveMode.Overwrite) - .json(mergeRelsPath); + .json(openorgsMergeRelsPath); } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/OrgSimRel.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/OrgSimRel.java new file mode 100644 index 000000000..65f383500 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/OrgSimRel.java @@ -0,0 +1,108 @@ + +package eu.dnetlib.dhp.oa.dedup.model; + +import java.io.Serializable; + +public class OrgSimRel implements Serializable { + + String local_id; + String oa_original_id; + String oa_name; + String oa_acronym; + String oa_country; + String oa_url; + String oa_collectedfrom; + String group_id; + + public OrgSimRel() { + } + + public OrgSimRel(String local_id, String oa_original_id, String oa_name, String oa_acronym, String oa_country, + String oa_url, String oa_collectedfrom, String group_id) { + this.local_id = local_id; + this.oa_original_id = oa_original_id; + this.oa_name = oa_name; + this.oa_acronym = oa_acronym; + this.oa_country = oa_country; + this.oa_url = oa_url; + this.oa_collectedfrom = oa_collectedfrom; + this.group_id = group_id; + } + + public String getLocal_id() { + return local_id; + } + + public void setLocal_id(String local_id) { + this.local_id = local_id; + } + + public String getOa_original_id() { + return oa_original_id; + } + + public void setOa_original_id(String oa_original_id) { + this.oa_original_id = oa_original_id; + } + + public String getOa_name() { + return oa_name; + } + + public void setOa_name(String oa_name) { + this.oa_name = oa_name; + } + + public String getOa_acronym() { + return oa_acronym; + } + + public void setOa_acronym(String oa_acronym) { + this.oa_acronym = oa_acronym; + } + + public String getOa_country() { + return oa_country; + } + + public void setOa_country(String oa_country) { + this.oa_country = oa_country; + } + + public String getOa_url() { + return oa_url; + } + + public void setOa_url(String oa_url) { + this.oa_url = oa_url; + } + + public String getOa_collectedfrom() { + return oa_collectedfrom; + } + + public void setOa_collectedfrom(String oa_collectedfrom) { + this.oa_collectedfrom = oa_collectedfrom; + } + + public String getGroup_id() { + return group_id; + } + + public void setGroup_id(String group_id) { + this.group_id = group_id; + } + + @Override + public String toString() { + return "OrgSimRel{" + + "local_id='" + local_id + '\'' + + ", oa_original_id='" + oa_original_id + '\'' + + ", oa_name='" + oa_name + '\'' + + ", oa_acronym='" + oa_acronym + '\'' + + ", oa_country='" + oa_country + '\'' + + ", oa_url='" + oa_url + '\'' + + ", oa_collectedfrom='" + oa_collectedfrom + '\'' + + '}'; + } +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml index 4c5505eb5..339e99084 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml @@ -79,7 +79,7 @@
- + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] @@ -88,8 +88,9 @@ + - + @@ -120,7 +121,7 @@ - + yarn @@ -139,6 +140,7 @@ --conf spark.sql.shuffle.partitions=3840 --graphBasePath${graphBasePath} + --isLookUpUrl${isLookUpUrl} --workingPath${workingPath} --actionSetId${actionSetId} --numPartitions8000 @@ -170,33 +172,6 @@ --actionSetId${actionSetId} --cutConnectedComponent${cutConnectedComponent} - - - - - - - yarn - cluster - Create Merge Relations - eu.dnetlib.dhp.oa.dedup.SparkRemoveDiffRels - dhp-dedup-openaire-${projectVersion}.jar - - --executor-memory=${sparkExecutorMemory} - --executor-cores=${sparkExecutorCores} - --driver-memory=${sparkDriverMemory} - --conf spark.extraListeners=${spark2ExtraListeners} - --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} - --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} - --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} - --conf spark.sql.shuffle.partitions=3840 - - --graphBasePath${graphBasePath} - --isLookUpUrl${isLookUpUrl} - --actionSetId${actionSetId} - --workingPath${workingPath} - --numPartitions8000 - diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareNewOrgs_parameters.json b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareNewOrgs_parameters.json new file mode 100644 index 000000000..b70d1af28 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareNewOrgs_parameters.json @@ -0,0 +1,62 @@ +[ + { + "paramName": "i", + "paramLongName": "graphBasePath", + "paramDescription": "the base path of raw graph", + "paramRequired": true + }, + { + "paramName": "w", + "paramLongName": "workingPath", + "paramDescription": "the working directory path", + "paramRequired": true + }, + { + "paramName": "la", + "paramLongName": "isLookUpUrl", + "paramDescription": "the url of the lookup service", + "paramRequired": true + }, + { + "paramName": "asi", + "paramLongName": "actionSetId", + "paramDescription": "the id of the actionset (orchestrator)", + "paramRequired": true + }, + { + "paramName": "nc", + "paramLongName": "numConnections", + "paramDescription": "number of connections to the postgres db (for the write operation)", + "paramRequired": false + }, + { + "paramName": "au", + "paramLongName": "apiUrl", + "paramDescription": "the url for the APIs of the openorgs service", + "paramRequired": false + }, + { + "paramName": "du", + "paramLongName": "dbUrl", + "paramDescription": "the url of the database", + "paramRequired": true + }, + { + "paramName": "dusr", + "paramLongName": "dbUser", + "paramDescription": "the user of the database", + "paramRequired": true + }, + { + "paramName": "t", + "paramLongName": "dbTable", + "paramDescription": "the name of the table in the database", + "paramRequired": true + }, + { + "paramName": "dpwd", + "paramLongName": "dbPwd", + "paramDescription": "the password for the user of the database", + "paramRequired": true + } +] \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json new file mode 100644 index 000000000..2119cbc3a --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json @@ -0,0 +1,56 @@ +[ + { + "paramName": "i", + "paramLongName": "graphBasePath", + "paramDescription": "the base path of raw graph", + "paramRequired": true + }, + { + "paramName": "w", + "paramLongName": "workingPath", + "paramDescription": "the working directory path", + "paramRequired": true + }, + { + "paramName": "la", + "paramLongName": "isLookUpUrl", + "paramDescription": "the url of the lookup service", + "paramRequired": true + }, + { + "paramName": "asi", + "paramLongName": "actionSetId", + "paramDescription": "the id of the actionset (orchestrator)", + "paramRequired": true + }, + { + "paramName": "nc", + "paramLongName": "numConnections", + "paramDescription": "number of connections to the postgres db (for the write operation)", + "paramRequired": false + }, + { + "paramName": "du", + "paramLongName": "dbUrl", + "paramDescription": "the url of the database", + "paramRequired": true + }, + { + "paramName": "dusr", + "paramLongName": "dbUser", + "paramDescription": "the user of the database", + "paramRequired": true + }, + { + "paramName": "t", + "paramLongName": "dbTable", + "paramDescription": "the name of the table in the database", + "paramRequired": true + }, + { + "paramName": "dpwd", + "paramLongName": "dbPwd", + "paramDescription": "the password for the user of the database", + "paramRequired": true + } +] \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java new file mode 100644 index 000000000..f8627d023 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java @@ -0,0 +1,296 @@ + +package eu.dnetlib.dhp.oa.dedup; + +import static java.nio.file.Files.createTempDirectory; + +import static org.apache.spark.sql.functions.count; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.lenient; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.net.URISyntaxException; +import java.nio.file.Paths; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaRDD; +import org.apache.spark.api.java.JavaSparkContext; +import org.apache.spark.api.java.function.FilterFunction; +import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.oaf.DataInfo; +import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.dhp.utils.ISLookupClientFactory; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import jdk.nashorn.internal.ir.annotations.Ignore; + +@ExtendWith(MockitoExtension.class) +public class SparkOpenorgsTest implements Serializable { + + @Mock(serializable = true) + ISLookUpService isLookUpService; + + private static SparkSession spark; + private static JavaSparkContext jsc; + + private static String testGraphBasePath; + private static String testOutputBasePath; + private static String testDedupGraphBasePath; + private static final String testActionSetId = "test-orchestrator"; + + protected static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + @BeforeAll + public static void cleanUp() throws IOException, URISyntaxException { + + testGraphBasePath = Paths + .get(SparkOpenorgsTest.class.getResource("/eu/dnetlib/dhp/dedup/entities").toURI()) + .toFile() + .getAbsolutePath(); + testOutputBasePath = createTempDirectory(SparkOpenorgsTest.class.getSimpleName() + "-") + .toAbsolutePath() + .toString(); + testDedupGraphBasePath = createTempDirectory(SparkOpenorgsTest.class.getSimpleName() + "-") + .toAbsolutePath() + .toString(); + +// FileUtils.deleteDirectory(new File(testOutputBasePath)); + FileUtils.deleteDirectory(new File(testDedupGraphBasePath)); + FileUtils.deleteDirectory(new File("/tmp/test-orchestrator/organization_openorgs_mergerels")); + + final SparkConf conf = new SparkConf(); + conf.set("spark.sql.shuffle.partitions", "200"); + spark = SparkSession + .builder() + .appName(SparkDedupTest.class.getSimpleName()) + .master("local[*]") + .config(conf) + .getOrCreate(); + + jsc = JavaSparkContext.fromSparkContext(spark.sparkContext()); + } + + @BeforeEach + public void setUp() throws IOException, ISLookUpException { + + lenient() + .when(isLookUpService.getResourceProfileByQuery(Mockito.contains(testActionSetId))) + .thenReturn( + IOUtils + .toString( + SparkDedupTest.class + .getResourceAsStream( + "/eu/dnetlib/dhp/dedup/profiles/mock_orchestrator_openorgs.xml"))); + + lenient() + .when(isLookUpService.getResourceProfileByQuery(Mockito.contains("organization"))) + .thenReturn( + IOUtils + .toString( + SparkDedupTest.class + .getResourceAsStream( + "/eu/dnetlib/dhp/dedup/conf/org.curr.conf.json"))); + } + + @Test + public void copyOpenorgsTest() throws Exception { + + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCopyOpenorgs.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/copyOpenorgs_parameters.json"))); + parser + .parseArgument( + new String[] { + "-i", testGraphBasePath, + "-asi", testActionSetId, + "-w", testOutputBasePath, + "-np", "50" + }); + + new SparkCopyOpenorgs(parser, spark).run(isLookUpService); + + long orgs_deduprecord = jsc + .textFile(testOutputBasePath + "/" + testActionSetId + "/organization_deduprecord") + .count(); + + assertEquals(0, orgs_deduprecord); + } + + @Test + public void copyOpenorgsMergeRels() throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCopyOpenorgsMergeRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/copyOpenorgsMergeRels_parameters.json"))); + parser + .parseArgument( + new String[] { + "-i", testGraphBasePath, + "-asi", testActionSetId, + "-w", testOutputBasePath, + "-la", "lookupurl", + "-np", "50" + }); + + new SparkCopyOpenorgsMergeRels(parser, spark).run(isLookUpService); + + long orgs_mergerel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/organization_mergerel") + .count(); + + assertEquals(0, orgs_mergerel); + + } + + @Test + public void copyOpenorgsSimRels() throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCopyOpenorgsSimRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/copyOpenorgsMergeRels_parameters.json"))); + parser + .parseArgument( + new String[] { + "-i", testGraphBasePath, + "-asi", testActionSetId, + "-w", testOutputBasePath, + "-la", "lookupurl", + "-np", "50" + }); + + new SparkCopyOpenorgsSimRels(parser, spark).run(isLookUpService); + + long orgs_simrel = spark + .read() + .textFile(testOutputBasePath + "/" + testActionSetId + "/organization_simrel") + .count(); + + System.out.println("orgs_simrel = " + orgs_simrel); + } + + @Test + public void createSimRelsTest() throws Exception { + + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCreateSimRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/createSimRels_parameters.json"))); + parser + .parseArgument( + new String[] { + "-i", testGraphBasePath, + "-asi", testActionSetId, + "-la", "lookupurl", + "-w", "/tmp", + "-np", "50" + }); + + new SparkCreateSimRels(parser, spark).run(isLookUpService); + + long orgs_simrel = spark + .read() + .textFile("/tmp/" + testActionSetId + "/organization_simrel") + .count(); + + assertEquals(3082, orgs_simrel); + } + + @Test + public void createMergeRelsTest() throws Exception { + + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCreateMergeRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/createCC_parameters.json"))); + parser + .parseArgument( + new String[] { + "-i", + testGraphBasePath, + "-asi", + testActionSetId, + "-la", + "lookupurl", + "-w", + "/tmp" + }); + + new SparkCreateMergeRels(parser, spark).run(isLookUpService); + + long orgs_mergerel = spark + .read() + .load("/tmp/" + testActionSetId + "/organization_mergerel") + .count(); + assertEquals(1272, orgs_mergerel); + } + + @Test + public void copyRelationsNoOpenorgsTest() throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCopyRelationsNoOpenorgs.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/updateEntity_parameters.json"))); + parser + .parseArgument( + new String[] { + "-i", testGraphBasePath, + "-w", testOutputBasePath, + "-o", testDedupGraphBasePath + }); + + new SparkCopyRelationsNoOpenorgs(parser, spark).run(isLookUpService); + + long relations = jsc.textFile(testDedupGraphBasePath + "/relation").count(); + +// Dataset relsRDD = spark.read().textFile(testDedupGraphBasePath + "/relation").map(patchRelFn(), Encoders.bean(Relation.class)); + + assertEquals(500, relations); + } + + @AfterAll + public static void finalCleanUp() throws IOException { + FileUtils.deleteDirectory(new File(testOutputBasePath)); + FileUtils.deleteDirectory(new File(testDedupGraphBasePath)); + } + + private static MapFunction patchRelFn() { + return value -> { + final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); + if (rel.getDataInfo() == null) { + rel.setDataInfo(new DataInfo()); + } + return rel; + }; + } +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/profiles/mock_orchestrator_openorgs.xml b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/profiles/mock_orchestrator_openorgs.xml new file mode 100644 index 000000000..59b6179ed --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/profiles/mock_orchestrator_openorgs.xml @@ -0,0 +1,24 @@ + +
+ + + + + +
+ + + + + + + + + + + + + + SECURITY_PARAMETERS + +
\ No newline at end of file diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java index 532bb43b2..3e5030eaa 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java @@ -612,7 +612,7 @@ public class MigrateDbEntitiesApplication extends AbstractMigrationApplication i final DataInfo info = prepareDataInfo(rs); // TODO final String orgId1 = createOpenaireId(20, rs.getString("id1"), true); - final String orgId2 = createOpenaireId(40, rs.getString("id2"), true); + final String orgId2 = createOpenaireId(20, rs.getString("id2"), true); final String relClass = rs.getString("relclass"); final List collectedFrom = listKeyValues( diff --git a/pom.xml b/pom.xml index a2e2587b3..25a52064a 100644 --- a/pom.xml +++ b/pom.xml @@ -114,9 +114,6 @@ test - - - From e76c4f62c1b1d6a35018f5fc27eb5377a9da94be Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Fri, 26 Feb 2021 10:58:48 +0100 Subject: [PATCH 129/445] MetadataRecord moved in dhp-schemas --- dhp-schemas/pom.xml | 5 +++++ .../dhp/schema/common/ModelSupport.java | 19 +++++++++++++++++++ .../dhp/schema}/mdstore/MetadataRecord.java | 12 +++++++----- .../dhp/schema}/mdstore/Provenance.java | 2 +- .../GenerateDataciteDatasetSpark.scala | 2 +- .../GenerateNativeStoreSparkJob.java | 4 ++-- .../transformation/TransformSparkJobNode.java | 3 +-- .../transformation/TransformationFactory.java | 2 +- .../xslt/XSLTTransformationFunction.java | 2 +- .../GenerateNativeStoreSparkJobTest.java | 4 ++-- .../transformation/TransformationJobTest.java | 2 +- 11 files changed, 41 insertions(+), 16 deletions(-) rename {dhp-common/src/main/java/eu/dnetlib/dhp/model => dhp-schemas/src/main/java/eu/dnetlib/dhp/schema}/mdstore/MetadataRecord.java (87%) rename {dhp-common/src/main/java/eu/dnetlib/dhp/model => dhp-schemas/src/main/java/eu/dnetlib/dhp/schema}/mdstore/Provenance.java (96%) diff --git a/dhp-schemas/pom.xml b/dhp-schemas/pom.xml index 10ee5f9ff..c4bb9e21f 100644 --- a/dhp-schemas/pom.xml +++ b/dhp-schemas/pom.xml @@ -67,6 +67,11 @@ guava + + commons-codec + commons-codec + + diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java index b5bca2e93..b08e41a55 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java @@ -3,11 +3,15 @@ package eu.dnetlib.dhp.schema.common; import static com.google.common.base.Preconditions.checkArgument; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.function.Function; +import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.Maps; @@ -473,4 +477,19 @@ public class ModelSupport { private static String idFnForOafEntity(T t) { return ((OafEntity) t).getId(); } + + public static String md5(final String s) { + try { + final MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(s.getBytes(StandardCharsets.UTF_8)); + return new String(Hex.encodeHex(md.digest())); + } catch (final NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + } + + public static String generateIdentifier(final String originalId, final String nsPrefix) { + return String.format("%s::%s", nsPrefix, md5(originalId)); + } + } diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/model/mdstore/MetadataRecord.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/mdstore/MetadataRecord.java similarity index 87% rename from dhp-common/src/main/java/eu/dnetlib/dhp/model/mdstore/MetadataRecord.java rename to dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/mdstore/MetadataRecord.java index 0b59dcce0..9586680e3 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/model/mdstore/MetadataRecord.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/mdstore/MetadataRecord.java @@ -1,12 +1,14 @@ -package eu.dnetlib.dhp.model.mdstore; +package eu.dnetlib.dhp.schema.mdstore; import java.io.Serializable; -import eu.dnetlib.dhp.utils.DHPUtils; +import eu.dnetlib.dhp.schema.common.ModelSupport; -/** This class models a record inside the new Metadata store collection on HDFS * */ -public class MetadataRecord implements Serializable { +/** + * This class models a record in a Metadata store collection on HDFS + */ + public class MetadataRecord implements Serializable { /** The D-Net Identifier associated to the record */ private String id; @@ -47,7 +49,7 @@ public class MetadataRecord implements Serializable { this.provenance = provenance; this.body = body; this.dateOfCollection = dateOfCollection; - this.id = DHPUtils.generateIdentifier(originalId, this.provenance.getNsPrefix()); + this.id = ModelSupport.generateIdentifier(originalId, this.provenance.getNsPrefix()); } public String getId() { diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/model/mdstore/Provenance.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/mdstore/Provenance.java similarity index 96% rename from dhp-common/src/main/java/eu/dnetlib/dhp/model/mdstore/Provenance.java rename to dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/mdstore/Provenance.java index 556535022..8af58f628 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/model/mdstore/Provenance.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/mdstore/Provenance.java @@ -1,5 +1,5 @@ -package eu.dnetlib.dhp.model.mdstore; +package eu.dnetlib.dhp.schema.mdstore; import java.io.Serializable; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/GenerateDataciteDatasetSpark.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/GenerateDataciteDatasetSpark.scala index f04f92c63..168ad218a 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/GenerateDataciteDatasetSpark.scala +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/GenerateDataciteDatasetSpark.scala @@ -2,7 +2,7 @@ package eu.dnetlib.dhp.actionmanager.datacite import eu.dnetlib.dhp.application.ArgumentApplicationParser import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup -import eu.dnetlib.dhp.model.mdstore.MetadataRecord +import eu.dnetlib.dhp.schema.mdstore.MetadataRecord import eu.dnetlib.dhp.schema.oaf.Oaf import eu.dnetlib.dhp.utils.ISLookupClientFactory import org.apache.spark.SparkConf diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java index ee82cc94f..043da31f9 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJob.java @@ -30,8 +30,8 @@ import org.slf4j.LoggerFactory; import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.model.mdstore.MetadataRecord; -import eu.dnetlib.dhp.model.mdstore.Provenance; +import eu.dnetlib.dhp.schema.mdstore.MetadataRecord; +import eu.dnetlib.dhp.schema.mdstore.Provenance; import scala.Tuple2; public class GenerateNativeStoreSparkJob { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java index cc130c376..6a0938708 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformSparkJobNode.java @@ -25,10 +25,9 @@ import eu.dnetlib.dhp.aggregation.common.AggregatorReport; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.message.MessageSender; -import eu.dnetlib.dhp.model.mdstore.MetadataRecord; +import eu.dnetlib.dhp.schema.mdstore.MetadataRecord; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import parquet.hadoop.ParquetReader; public class TransformSparkJobNode { diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java index 45ba2981f..096d0e289 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/TransformationFactory.java @@ -11,7 +11,7 @@ import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.aggregation.common.AggregationCounter; import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; -import eu.dnetlib.dhp.model.mdstore.MetadataRecord; +import eu.dnetlib.dhp.schema.mdstore.MetadataRecord; import eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java index a813d84db..d9b38e572 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java @@ -10,7 +10,7 @@ import org.apache.spark.api.java.function.MapFunction; import eu.dnetlib.dhp.aggregation.common.AggregationCounter; import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; -import eu.dnetlib.dhp.model.mdstore.MetadataRecord; +import eu.dnetlib.dhp.schema.mdstore.MetadataRecord; import net.sf.saxon.s9api.*; public class XSLTTransformationFunction implements MapFunction { diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJobTest.java index 723f030a6..b8eb58ec2 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/GenerateNativeStoreSparkJobTest.java @@ -38,8 +38,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.data.mdstore.manager.common.model.MDStoreVersion; import eu.dnetlib.dhp.aggregation.AbstractVocabularyTest; -import eu.dnetlib.dhp.model.mdstore.MetadataRecord; -import eu.dnetlib.dhp.model.mdstore.Provenance; +import eu.dnetlib.dhp.schema.mdstore.MetadataRecord; +import eu.dnetlib.dhp.schema.mdstore.Provenance; import eu.dnetlib.dhp.transformation.TransformSparkJobNode; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java index 3c0c8bf0f..e29a8ac50 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java @@ -26,7 +26,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import eu.dnetlib.dhp.aggregation.AbstractVocabularyTest; import eu.dnetlib.dhp.aggregation.common.AggregationCounter; -import eu.dnetlib.dhp.model.mdstore.MetadataRecord; +import eu.dnetlib.dhp.schema.mdstore.MetadataRecord; import eu.dnetlib.dhp.transformation.xslt.DateCleaner; import eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; From b73dce3e3a1a97aa899e1ef2b8a92067026d2508 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 3 Mar 2021 10:17:16 +0100 Subject: [PATCH 130/445] more logging on the MDStore mongodb client. Forcing UTF_8 encoding on the content --- .../java/eu/dnetlib/dhp/common/MdstoreClient.java | 11 +++++++++-- .../plugin/mongodb/MDStoreCollectorPlugin.java | 7 +++++++ .../xslt/XSLTTransformationFunction.java | 8 ++++++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/common/MdstoreClient.java b/dhp-common/src/main/java/eu/dnetlib/dhp/common/MdstoreClient.java index d29498306..38837b557 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/common/MdstoreClient.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/common/MdstoreClient.java @@ -21,17 +21,19 @@ import com.mongodb.MongoClientURI; import com.mongodb.QueryBuilder; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class MdstoreClient implements Closeable { + private static final Logger log = LoggerFactory.getLogger(MdstoreClient.class); + private final MongoClient client; private final MongoDatabase db; private static final String COLL_METADATA = "metadata"; private static final String COLL_METADATA_MANAGER = "metadataManager"; - private static final Log log = LogFactory.getLog(MdstoreClient.class); - public MdstoreClient(final String baseUrl, final String dbName) { this.client = new MongoClient(new MongoClientURI(baseUrl)); this.db = getDb(client, dbName); @@ -40,11 +42,16 @@ public class MdstoreClient implements Closeable { public MongoCollection mdStore(final String mdId) { BasicDBObject query = (BasicDBObject) QueryBuilder.start("mdId").is(mdId).get(); + log.info("querying current mdId: {}", query.toJson()); + final String currentId = Optional .ofNullable(getColl(db, COLL_METADATA_MANAGER, true).find(query)) .map(r -> r.first()) .map(d -> d.getString("currentId")) .orElseThrow(() -> new IllegalArgumentException("cannot find current mdstore id for: " + mdId)); + + log.info("currentId: {}", currentId); + return getColl(db, currentId, true); } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MDStoreCollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MDStoreCollectorPlugin.java index 77e899cc9..549c59720 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MDStoreCollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/mongodb/MDStoreCollectorPlugin.java @@ -8,6 +8,8 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; import org.bson.Document; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.mongodb.client.MongoCollection; @@ -19,6 +21,8 @@ import eu.dnetlib.dhp.common.MdstoreClient; public class MDStoreCollectorPlugin implements CollectorPlugin { + private static final Logger log = LoggerFactory.getLogger(MDStoreCollectorPlugin.class); + public static final String MONGODB_DBNAME = "mongodb_dbname"; public static final String MDSTORE_ID = "mdstore_id"; @@ -30,14 +34,17 @@ public class MDStoreCollectorPlugin implements CollectorPlugin { .orElseThrow( () -> new CollectorException( "missing mongodb baseUrl, expected in eu.dnetlib.dhp.collection.ApiDescriptor.baseUrl")); + log.info("mongoBaseUrl: {}", mongoBaseUrl); final String dbName = Optional .ofNullable(api.getParams().get(MONGODB_DBNAME)) .orElseThrow(() -> new CollectorException(String.format("missing parameter '%s'", MONGODB_DBNAME))); + log.info("dbName: {}", dbName); final String mdId = Optional .ofNullable(api.getParams().get(MDSTORE_ID)) .orElseThrow(() -> new CollectorException(String.format("missing parameter '%s'", MDSTORE_ID))); + log.info("mdId: {}", mdId); final MdstoreClient client = new MdstoreClient(mongoBaseUrl, dbName); final MongoCollection mdstore = client.mdStore(mdId); diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java index d9b38e572..430fbcf95 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java @@ -3,9 +3,11 @@ package eu.dnetlib.dhp.transformation.xslt; import java.io.ByteArrayInputStream; import java.io.StringWriter; +import java.nio.charset.StandardCharsets; import javax.xml.transform.stream.StreamSource; +import org.apache.commons.io.IOUtils; import org.apache.spark.api.java.function.MapFunction; import eu.dnetlib.dhp.aggregation.common.AggregationCounter; @@ -44,18 +46,20 @@ public class XSLTTransformationFunction implements MapFunction Date: Wed, 3 Mar 2021 10:22:29 +0100 Subject: [PATCH 131/445] removed unused classes --- .../dhp/transformation/vocabulary/Term.java | 53 ------------------ .../transformation/vocabulary/Vocabulary.java | 54 ------------------- .../vocabulary/VocabularyHelper.java | 24 --------- .../vocabulary/VocabularyTest.java | 16 ------ 4 files changed, 147 deletions(-) delete mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/vocabulary/Term.java delete mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/vocabulary/Vocabulary.java delete mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/vocabulary/VocabularyHelper.java delete mode 100644 dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/vocabulary/VocabularyTest.java diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/vocabulary/Term.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/vocabulary/Term.java deleted file mode 100644 index b5ac18169..000000000 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/vocabulary/Term.java +++ /dev/null @@ -1,53 +0,0 @@ - -package eu.dnetlib.dhp.transformation.vocabulary; - -import java.io.Serializable; - -public class Term implements Serializable { - - private String englishName; - private String nativeName; - private String encoding; - private String code; - private String synonyms; - - public String getEnglishName() { - return englishName; - } - - public void setEnglishName(String englishName) { - this.englishName = englishName; - } - - public String getNativeName() { - return nativeName; - } - - public void setNativeName(String nativeName) { - this.nativeName = nativeName; - } - - public String getEncoding() { - return encoding; - } - - public void setEncoding(String encoding) { - this.encoding = encoding; - } - - public String getCode() { - return code; - } - - public void setCode(String code) { - this.code = code; - } - - public String getSynonyms() { - return synonyms; - } - - public void setSynonyms(String synonyms) { - this.synonyms = synonyms; - } -} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/vocabulary/Vocabulary.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/vocabulary/Vocabulary.java deleted file mode 100644 index a9da6b725..000000000 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/vocabulary/Vocabulary.java +++ /dev/null @@ -1,54 +0,0 @@ - -package eu.dnetlib.dhp.transformation.vocabulary; - -import java.io.Serializable; -import java.util.List; - -public class Vocabulary implements Serializable { - - private String id; - private String name; - private String description; - private String code; - private List terms; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public String getCode() { - return code; - } - - public void setCode(String code) { - this.code = code; - } - - public List getTerms() { - return terms; - } - - public void setTerms(List terms) { - this.terms = terms; - } -} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/vocabulary/VocabularyHelper.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/vocabulary/VocabularyHelper.java deleted file mode 100644 index 10e959be0..000000000 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/vocabulary/VocabularyHelper.java +++ /dev/null @@ -1,24 +0,0 @@ - -package eu.dnetlib.dhp.transformation.vocabulary; - -import java.io.Serializable; -import java.net.URL; -import java.nio.charset.Charset; - -import org.apache.commons.io.IOUtils; - -import com.fasterxml.jackson.databind.ObjectMapper; - -public class VocabularyHelper implements Serializable { - - private static final String OPENAIRE_URL = "http://api.openaire.eu/vocabularies/%s.json"; - - public static Vocabulary getVocabularyFromAPI(final String vocabularyName) throws Exception { - final URL url = new URL(String.format(OPENAIRE_URL, vocabularyName)); - - final String response = IOUtils.toString(url, Charset.defaultCharset()); - final ObjectMapper jsonMapper = new ObjectMapper(); - final Vocabulary vocabulary = jsonMapper.readValue(response, Vocabulary.class); - return vocabulary; - } -} diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/vocabulary/VocabularyTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/vocabulary/VocabularyTest.java deleted file mode 100644 index 1ae942a6b..000000000 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/vocabulary/VocabularyTest.java +++ /dev/null @@ -1,16 +0,0 @@ - -package eu.dnetlib.dhp.transformation.vocabulary; - -import static org.junit.jupiter.api.Assertions.*; - -import org.junit.jupiter.api.Test; - -public class VocabularyTest { - - @Test - public void testLoadVocabulary() throws Exception { - - final Vocabulary vocabulary = VocabularyHelper.getVocabularyFromAPI("dnet:languages"); - assertEquals("dnet:languages", vocabulary.getName()); - } -} From ec80b7ade3dd631874eb78296ce52da3b2205812 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 3 Mar 2021 10:22:53 +0100 Subject: [PATCH 132/445] code formatting --- .../src/main/java/eu/dnetlib/dhp/common/MdstoreClient.java | 4 ++-- .../java/eu/dnetlib/dhp/schema/mdstore/MetadataRecord.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/common/MdstoreClient.java b/dhp-common/src/main/java/eu/dnetlib/dhp/common/MdstoreClient.java index 38837b557..0bc782ccb 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/common/MdstoreClient.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/common/MdstoreClient.java @@ -13,6 +13,8 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.bson.Document; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.google.common.collect.Iterables; import com.mongodb.BasicDBObject; @@ -21,8 +23,6 @@ import com.mongodb.MongoClientURI; import com.mongodb.QueryBuilder; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class MdstoreClient implements Closeable { diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/mdstore/MetadataRecord.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/mdstore/MetadataRecord.java index 9586680e3..8277e1469 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/mdstore/MetadataRecord.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/mdstore/MetadataRecord.java @@ -8,7 +8,7 @@ import eu.dnetlib.dhp.schema.common.ModelSupport; /** * This class models a record in a Metadata store collection on HDFS */ - public class MetadataRecord implements Serializable { +public class MetadataRecord implements Serializable { /** The D-Net Identifier associated to the record */ private String id; From 55f6ff5f559a7e765b8d0a911fe6ca330bcf7d83 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 3 Mar 2021 16:18:34 +0100 Subject: [PATCH 133/445] README.md for aggregation workflows --- dhp-workflows/dhp-aggregation/README.md | 27 +++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/README.md b/dhp-workflows/dhp-aggregation/README.md index e46fdeb16..5ed6a82d7 100644 --- a/dhp-workflows/dhp-aggregation/README.md +++ b/dhp-workflows/dhp-aggregation/README.md @@ -1,16 +1,27 @@ Description of the Module -------------------------- -This module defines a **collector worker application** that runs on Hadoop. +This module defines a set of oozie workflows for the **collection** and **transformation** of metadata records. +Both workflows interact with the Metadata Store Manager (MdSM) to handle the logical transactions required to ensure +the consistency of the read/write operations on the data as the MdSM in fact keeps track of the logical-physical mapping +of each MDStore. -It is responsible for harvesting metadata using different collector plugins and transformation into the common metadata model. +## Metadata collection -# Collector Plugins -* OAI Plugin +The **metadata collection workflow** is responsible for harvesting metadata records from different protocols and responding to +different formats and to store them as on HDFS so that they can be further processed. + +### Collector Plugins + +Different protocols are managed by dedicated Collector plugins, i.e. java programs implementing a defined interface: + +```eu.dnetlib.dhp.collection.plugin.CollectorPlugin``` + +The list of the supported plugins: + +* OAI Plugin: collects from OAI-PMH compatible endpoints +* MDStore plugin: collects from a given D-Net MetadataStore, (identified by moogodb URI, dbName, MDStoreID) +* MDStore dump plugin: collects from an MDStore dump stored on the HDFS location indicated by the `path` parameter # Transformation Plugins TODO - -# Usage -TODO - From fa7930d2e2c4aeda1ee42018be065826367dc96e Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Fri, 5 Mar 2021 15:45:28 +0100 Subject: [PATCH 134/445] merging contributions from PR#97 --- .gitignore | 3 + .../common/vocabulary/VocabularyGroup.java | 24 + .../transformation/TransformationJobTest.java | 122 +- .../eu/dnetlib/dhp/transform/input_itgv4.xml | 70 ++ .../xslt_cleaning_datarepo_datacite.xsl | 432 +++++++ .../xslt_cleaning_datarepo_datacite_orig.xsl | 472 +++++++ .../scripts/xslt_cleaning_oaiOpenaire.xsl | 82 ++ ...enaire_datacite_ExchangeLandingpagePid.xsl | 791 ++++++++++++ ...e_datacite_ExchangeLandingpagePid_orig.xsl | 1081 +++++++++++++++++ .../dhp/transform/scripts/zenodo_tr.xsl | 451 +++++++ dhp-workflows/dhp-graph-mapper/pom.xml | 6 +- pom.xml | 28 +- 12 files changed, 3546 insertions(+), 16 deletions(-) create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input_itgv4.xml create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_datarepo_datacite.xsl create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_datarepo_datacite_orig.xsl create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_oaiOpenaire.xsl create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_oaiOpenaire_datacite_ExchangeLandingpagePid.xsl create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_oaiOpenaire_datacite_ExchangeLandingpagePid_orig.xsl create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/zenodo_tr.xsl diff --git a/.gitignore b/.gitignore index 2d7730711..f4fb46f2e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ *.iws *~ .vscode +.metals +.bloop .classpath /*/.classpath /*/*/.classpath @@ -24,4 +26,5 @@ spark-warehouse /**/job-override.properties /**/*.log +/**/.factorypath diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary/VocabularyGroup.java b/dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary/VocabularyGroup.java index f81181e53..12c6279e5 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary/VocabularyGroup.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/common/vocabulary/VocabularyGroup.java @@ -122,7 +122,31 @@ public class VocabularyGroup implements Serializable { return vocs.get(vocId.toLowerCase()).getSynonymAsQualifier(syn); } + /** + * getSynonymAsQualifierCaseSensitive + * + * refelects the situation to check caseSensitive vocabulary + */ + public Qualifier getSynonymAsQualifierCaseSensitive(final String vocId, final String syn) { + if (StringUtils.isBlank(vocId)) { + return OafMapperUtils.unknown("", ""); + } + return vocs.get(vocId).getSynonymAsQualifier(syn); + } + + /** + * termExists + * + * two methods: without and with caseSensitive check + */ public boolean termExists(final String vocId, final String id) { + return termExists(vocId, id, Boolean.FALSE); + } + + public boolean termExists(final String vocId, final String id, final Boolean caseSensitive) { + if (Boolean.TRUE.equals(caseSensitive)) { + return vocabularyExists(vocId) && vocs.get(vocId).termExists(id); + } return vocabularyExists(vocId) && vocs.get(vocId.toLowerCase()).termExists(id); } diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java index e29a8ac50..62a5223d9 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java @@ -51,13 +51,12 @@ public class TransformationJobTest extends AbstractVocabularyTest { } @Test - @DisplayName("Test Transform Single XML using XSLTTransformator") + @DisplayName("Test Transform Single XML using zenodo_tr XSLTTransformator") public void testTransformSaxonHE() throws Exception { // We Set the input Record getting the XML from the classpath final MetadataRecord mr = new MetadataRecord(); mr.setBody(IOUtils.toString(getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/input_zenodo.xml"))); - // We Load the XSLT transformation Rule from the classpath XSLTTransformationFunction tr = loadTransformationRule("/eu/dnetlib/dhp/transform/zenodo_tr.xslt"); @@ -68,6 +67,125 @@ public class TransformationJobTest extends AbstractVocabularyTest { // TODO Create significant Assert } + @Test + @DisplayName("Test Transform Inst.&Them.v4 record XML with zenodo_tr") + public void testTransformITGv4Zenodo() throws Exception { + + // We Set the input Record getting the XML from the classpath + final MetadataRecord mr = new MetadataRecord(); + mr.setBody(IOUtils.toString(getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/input_itgv4.xml"))); + // We Load the XSLT transformation Rule from the classpath + XSLTTransformationFunction tr = loadTransformationRule("/eu/dnetlib/dhp/transform/zenodo_tr.xslt"); + + MetadataRecord result = tr.call(mr); + + // Print the record + System.out.println(result.getBody()); + // TODO Create significant Assert + } + + @Test + @DisplayName("Test Transform Inst.&Them.v4 record XML with xslt_cleaning_oaiOpenaire_datacite_ExchangeLandingpagePid") + public void testTransformITGv4() throws Exception { + + // We Set the input Record getting the XML from the classpath + final MetadataRecord mr = new MetadataRecord(); + mr.setBody(IOUtils.toString(getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/input_itgv4.xml"))); + // We Load the XSLT transformation Rule from the classpath + XSLTTransformationFunction tr = loadTransformationRule( + "/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_oaiOpenaire_datacite_ExchangeLandingpagePid.xsl"); + + MetadataRecord result = tr.call(mr); + + // Print the record + System.out.println(result.getBody()); + // TODO Create significant Assert + } + + @Test + @DisplayName("Test Transform record XML with xslt_cleaning_datarepo_datacite") + public void testTransformMostlyUsedScript() throws Exception { + + // We Set the input Record getting the XML from the classpath + final MetadataRecord mr = new MetadataRecord(); + mr.setBody(IOUtils.toString(getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/input_itgv4.xml"))); + // We Load the XSLT transformation Rule from the classpath + XSLTTransformationFunction tr = loadTransformationRule( + "/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_datarepo_datacite.xsl"); + + MetadataRecord result = tr.call(mr); + + // Print the record + System.out.println(result.getBody()); + // TODO Create significant Assert + } + + @Test + @DisplayName("Test TransformSparkJobNode.main with oaiOpenaire_datacite (v4)") + public void transformTestITGv4OAIdatacite(@TempDir Path testDir) throws Exception { + + SparkConf conf = new SparkConf(); + conf.setAppName(TransformationJobTest.class.getSimpleName()); + conf.setMaster("local"); + + try (SparkSession spark = SparkSession.builder().config(conf).getOrCreate()) { + + final String mdstore_input = this + .getClass() + .getResource("/eu/dnetlib/dhp/transform/mdstorenative") + .getFile(); + final String mdstore_output = testDir.toString() + "/version"; + + mockupTrasformationRule( + "simpleTRule", + "/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_oaiOpenaire_datacite_ExchangeLandingpagePid.xsl"); + + final Map parameters = Stream.of(new String[][] { + { + "dateOfTransformation", "1234" + }, + { + "varOfficialName", "Publications at Bielefeld University" + }, + { + "varOfficialId", "opendoar____::2294" + }, + { + "transformationPlugin", "XSLT_TRANSFORM" + }, + { + "transformationRuleId", "simpleTRule" + }, + + }).collect(Collectors.toMap(data -> data[0], data -> data[1])); + + TransformSparkJobNode.transformRecords(parameters, isLookUpService, spark, mdstore_input, mdstore_output); + + // TODO introduce useful assertions + + final Encoder encoder = Encoders.bean(MetadataRecord.class); + final Dataset mOutput = spark + .read() + .format("parquet") + .load(mdstore_output + MDSTORE_DATA_PATH) + .as(encoder); + + final Long total = mOutput.count(); + + final long recordTs = mOutput + .filter((FilterFunction) p -> p.getDateOfTransformation() == 1234) + .count(); + + final long recordNotEmpty = mOutput + .filter((FilterFunction) p -> !StringUtils.isBlank(p.getBody())) + .count(); + + assertEquals(total, recordTs); + + assertEquals(total, recordNotEmpty); + } + } + @Test @DisplayName("Test TransformSparkJobNode.main") public void transformTest(@TempDir Path testDir) throws Exception { diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input_itgv4.xml b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input_itgv4.xml new file mode 100644 index 000000000..06325810b --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input_itgv4.xml @@ -0,0 +1,70 @@ + + + + + od______2294::0000955eab68583ba0e07e973dd48708 + oai:pub.uni-bielefeld.de:1997560 + 2021-02-23T13:14:00.839Z + od______2294 + oai:pub.uni-bielefeld.de:1997560 + 2018-07-24T12:58:03Z + journal_article + doc-type:article + + + + Die antiken Grundlagen der europäischen Expansion. Eine epochenübergreifende kulturhistorische Unterrichtseinheit + + + Schulz, Raimund + + + + https://pub.uni-bielefeld.de/record/1997560.json + + + 0016-9056 + + ger + Friedrich + 2002 + journal article + https://pub.uni-bielefeld.de/record/1997560 + metadata only access + Schulz R. Die antiken Grundlagen der europäischen Expansion. Eine epochenübergreifende kulturhistorische Unterrichtseinheit. GWU. 2002;53(5-/6):340-360. + + In Copyright + GWU + 53 + 5-/6 + + + + + + http%3A%2F%2Fpub.uni-bielefeld.de%2Foai + oai:pub.uni-bielefeld.de:1997560 + 2018-07-24T12:58:03Z + + + + + false + false + 0.9 + + + + + + \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_datarepo_datacite.xsl b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_datarepo_datacite.xsl new file mode 100644 index 000000000..f815c0260 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_datarepo_datacite.xsl @@ -0,0 +1,432 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + record is not compliant, transformation is interrupted. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OPEN + + + + + OPEN + + + + + RESTRICTED + + + + + UNKNOWN + + + + + + + + + + + + + + + DE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_datarepo_datacite_orig.xsl b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_datarepo_datacite_orig.xsl new file mode 100644 index 000000000..d8b14fadd --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_datarepo_datacite_orig.xsl @@ -0,0 +1,472 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + record is not compliant, transformation is interrupted. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OPEN + + + + + OPEN + + + + + RESTRICTED + + + + + UNKNOWN + + + + + + + + + + + + + + + DE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_oaiOpenaire.xsl b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_oaiOpenaire.xsl new file mode 100644 index 000000000..53a3466a9 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_oaiOpenaire.xsl @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + record is not compliant, transformation is interrupted. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_oaiOpenaire_datacite_ExchangeLandingpagePid.xsl b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_oaiOpenaire_datacite_ExchangeLandingpagePid.xsl new file mode 100644 index 000000000..56451505e --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_oaiOpenaire_datacite_ExchangeLandingpagePid.xsl @@ -0,0 +1,791 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + record is not compliant, transformation is interrupted. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_oaiOpenaire_datacite_ExchangeLandingpagePid_orig.xsl b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_oaiOpenaire_datacite_ExchangeLandingpagePid_orig.xsl new file mode 100644 index 000000000..3cfaec80b --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_oaiOpenaire_datacite_ExchangeLandingpagePid_orig.xsl @@ -0,0 +1,1081 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + record is not compliant, transformation is interrupted. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/zenodo_tr.xsl b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/zenodo_tr.xsl new file mode 100644 index 000000000..0c3f4b1f9 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/zenodo_tr.xsl @@ -0,0 +1,451 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OPEN + + + + + CLOSED + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dhp-workflows/dhp-graph-mapper/pom.xml b/dhp-workflows/dhp-graph-mapper/pom.xml index 5e8448182..81d93f97b 100644 --- a/dhp-workflows/dhp-graph-mapper/pom.xml +++ b/dhp-workflows/dhp-graph-mapper/pom.xml @@ -33,6 +33,10 @@ + + -Xmax-classfile-name + 140 + ${scala.version} @@ -67,7 +71,7 @@ test - org.apache.httpcomponents + org.apache.httpcomponents httpclient diff --git a/pom.xml b/pom.xml index bef649c67..45bb6bf78 100644 --- a/pom.xml +++ b/pom.xml @@ -362,7 +362,7 @@ ${dnet.openaire.broker.common} - + org.apache.cxf cxf-rt-transports-http 3.1.5 @@ -406,20 +406,20 @@ 4.0 - - com.ximpleware - vtd-xml - ${vtd.version} - + + com.ximpleware + vtd-xml + ${vtd.version} + - - org.elasticsearch - elasticsearch-hadoop - 7.6.0 - + + org.elasticsearch + elasticsearch-hadoop + 7.6.0 + - + org.apache.oozie oozie-client ${dhp.oozie.version} @@ -685,6 +685,8 @@ UTF-8 UTF-8 3.6.0 + 1.8 + 1.8 2.22.2 2.0.1 cdh5.9.2 @@ -711,4 +713,4 @@ 4.5.3 4.0.1 - + \ No newline at end of file From acbe3119a4a5510bfd6ab887c6165e96947e6409 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 8 Mar 2021 09:44:09 +0100 Subject: [PATCH 135/445] RestCollectorPlugin imported from dne45 --- dhp-workflows/dhp-aggregation/pom.xml | 5 + .../dhp/collection/CollectorWorker.java | 3 + .../eu/dnetlib/dhp/collection/JsonUtils.java | 84 ++++ .../collection/plugin/CollectorPlugin.java | 2 +- .../plugin/rest/RestCollectorPlugin.java | 92 ++++ .../collection/plugin/rest/RestIterator.java | 442 ++++++++++++++++++ .../plugin/rest/RestCollectorPluginTest.java | 81 ++++ .../plugin/rest/RestIteratorTest.java | 54 +++ pom.xml | 6 + 9 files changed, 768 insertions(+), 1 deletion(-) create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/JsonUtils.java create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/rest/RestCollectorPlugin.java create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/rest/RestIterator.java create mode 100644 dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/plugin/rest/RestCollectorPluginTest.java create mode 100644 dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/plugin/rest/RestIteratorTest.java diff --git a/dhp-workflows/dhp-aggregation/pom.xml b/dhp-workflows/dhp-aggregation/pom.xml index 6887be55e..cfe9e74fd 100644 --- a/dhp-workflows/dhp-aggregation/pom.xml +++ b/dhp-workflows/dhp-aggregation/pom.xml @@ -86,6 +86,11 @@ jaxen + + org.json + json + + org.apache.commons diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java index ef29cb5b1..f9d7d7dae 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/CollectorWorker.java @@ -24,6 +24,7 @@ import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; import eu.dnetlib.dhp.collection.plugin.mongodb.MDStoreCollectorPlugin; import eu.dnetlib.dhp.collection.plugin.mongodb.MongoDbDumpCollectorPlugin; import eu.dnetlib.dhp.collection.plugin.oai.OaiCollectorPlugin; +import eu.dnetlib.dhp.collection.plugin.rest.RestCollectorPlugin; public class CollectorWorker extends ReportingJob { @@ -109,6 +110,8 @@ public class CollectorWorker extends ReportingJob { switch (CollectorPlugin.NAME.valueOf(api.getProtocol())) { case oai: return new OaiCollectorPlugin(clientParams); + case rest_json2xml: + return new RestCollectorPlugin(clientParams); case other: final CollectorPlugin.NAME.OTHER_NAME plugin = Optional .ofNullable(api.getParams().get("other_plugin_type")) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/JsonUtils.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/JsonUtils.java new file mode 100644 index 000000000..da3768a4a --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/JsonUtils.java @@ -0,0 +1,84 @@ + +package eu.dnetlib.dhp.collection; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class JsonUtils { + + private static final Log log = LogFactory.getLog(JsonUtils.class); + + public static final String wrapName = "recordWrap"; + + /** + * convert in JSON-KeyName 'whitespace(s)' to '_' and '/' to '_', '(' and ')' to '' + * check W3C XML syntax: https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-starttags for valid tag names + * and work-around for the JSON to XML converting of org.json.XML-package. + * + * known bugs: doesn't prevent "key name":" ["sexy name",": penari","erotic dance"], + * + * @param jsonInput + * @return convertedJsonKeynameOutput + */ + public String syntaxConvertJsonKeyNames(String jsonInput) { + + log.trace("before convertJsonKeyNames: " + jsonInput); + // pre-clean json - rid spaces of element names (misinterpreted as elements with attributes in xml) + // replace ' 's in JSON Namens with '_' + while (jsonInput.matches(".*\"([^\"]*)\\s+([^\"]*)\":.*")) { + jsonInput = jsonInput.replaceAll("\"([^\"]*)\\s+([^\"]*)\":", "\"$1_$2\":"); + } + + // replace forward-slash (sign '/' ) in JSON Names with '_' + while (jsonInput.matches(".*\"([^\"]*)/([^\"]*)\":.*")) { + jsonInput = jsonInput.replaceAll("\"([^\"]*)/([^\"]*)\":", "\"$1_$2\":"); + } + + // replace '(' in JSON Names with '' + while (jsonInput.matches(".*\"([^\"]*)[(]([^\"]*)\":.*")) { + jsonInput = jsonInput.replaceAll("\"([^\"]*)[(]([^\"]*)\":", "\"$1$2\":"); + } + + // replace ')' in JSON Names with '' + while (jsonInput.matches(".*\"([^\"]*)[)]([^\"]*)\":.*")) { + jsonInput = jsonInput.replaceAll("\"([^\"]*)[)]([^\"]*)\":", "\"$1$2\":"); + } + + // add prefix of startNumbers in JSON Keynames with 'n_' + while (jsonInput.matches(".*\"([^\"][0-9])([^\"]*)\":.*")) { + jsonInput = jsonInput.replaceAll("\"([^\"][0-9])([^\"]*)\":", "\"n_$1$2\":"); + } + // add prefix of only numbers in JSON Keynames with 'm_' + while (jsonInput.matches(".*\"([0-9]+)\":.*")) { + jsonInput = jsonInput.replaceAll("\"([0-9]+)\":", "\"m_$1\":"); + } + + // replace ':' between number like '2018-08-28T11:05:00Z' in JSON keynames with '' + while (jsonInput.matches(".*\"([^\"]*[0-9]):([0-9][^\"]*)\":.*")) { + jsonInput = jsonInput.replaceAll("\"([^\"]*[0-9]):([0-9][^\"]*)\":", "\"$1$2\":"); + } + + // replace ',' in JSON Keynames with '.' to prevent , in xml tagnames. + // while (jsonInput.matches(".*\"([^\"]*),([^\"]*)\":.*")) { + // jsonInput = jsonInput.replaceAll("\"([^\"]*),([^\"]*)\":", "\"$1.$2\":"); + // } + + // replace '=' in JSON Keynames with '-' + while (jsonInput.matches(".*\"([^\"]*)=([^\"]*)\":.*")) { + jsonInput = jsonInput.replaceAll("\"([^\"]*)=([^\"]*)\":", "\"$1-$2\":"); + } + + log.trace("after syntaxConvertJsonKeyNames: " + jsonInput); + return jsonInput; + } + + public String convertToXML(final String jsonRecord) { + String resultXml = ""; + org.json.JSONObject jsonObject = new org.json.JSONObject(syntaxConvertJsonKeyNames(jsonRecord)); + resultXml += org.json.XML.toString(jsonObject, wrapName); // wrap xml in single root element + log.trace("before inputStream: " + resultXml); + resultXml = XmlCleaner.cleanAllEntities(resultXml); + log.trace("after cleaning: " + resultXml); + return resultXml; + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java index 0ed6be5fa..457f63468 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/CollectorPlugin.java @@ -10,7 +10,7 @@ import eu.dnetlib.dhp.collection.CollectorException; public interface CollectorPlugin { enum NAME { - oai, other; + oai, other, rest_json2xml; public enum OTHER_NAME { mdstore_mongodb_dump, mdstore_mongodb diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/rest/RestCollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/rest/RestCollectorPlugin.java new file mode 100644 index 000000000..ad8bfa4ea --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/rest/RestCollectorPlugin.java @@ -0,0 +1,92 @@ + +package eu.dnetlib.dhp.collection.plugin.rest; + +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.apache.commons.lang3.StringUtils; + +import eu.dnetlib.dhp.aggregation.common.AggregatorReport; +import eu.dnetlib.dhp.collection.ApiDescriptor; +import eu.dnetlib.dhp.collection.CollectorException; +import eu.dnetlib.dhp.collection.HttpClientParams; +import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; + +/** + * TODO: delegate HTTP requests to the common HttpConnector2 implementation. + * + * @author js, Andreas Czerniak + * @date 2020-04-09 + * + */ +public class RestCollectorPlugin implements CollectorPlugin { + + private HttpClientParams clientParams; + + public RestCollectorPlugin(HttpClientParams clientParams) { + this.clientParams = clientParams; + } + + @Override + public Stream collect(final ApiDescriptor api, final AggregatorReport report) throws CollectorException { + final String baseUrl = api.getBaseUrl(); + final String resumptionType = api.getParams().get("resumptionType"); + final String resumptionParam = api.getParams().get("resumptionParam"); + final String resumptionXpath = api.getParams().get("resumptionXpath"); + final String resultTotalXpath = api.getParams().get("resultTotalXpath"); + final String resultFormatParam = api.getParams().get("resultFormatParam"); + final String resultFormatValue = api.getParams().get("resultFormatValue"); + final String resultSizeParam = api.getParams().get("resultSizeParam"); + final String resultSizeValue = (StringUtils.isBlank(api.getParams().get("resultSizeValue"))) ? "100" + : api.getParams().get("resultSizeValue"); + final String queryParams = api.getParams().get("queryParams"); + final String entityXpath = api.getParams().get("entityXpath"); + final String authMethod = api.getParams().get("authMethod"); + final String authToken = api.getParams().get("authToken"); + + if (StringUtils.isBlank(baseUrl)) { + throw new CollectorException("Param 'baseUrl' is null or empty"); + } + if (StringUtils.isBlank(resumptionType)) { + throw new CollectorException("Param 'resumptionType' is null or empty"); + } + if (StringUtils.isBlank(resumptionParam)) { + throw new CollectorException("Param 'resumptionParam' is null or empty"); + } + if (StringUtils.isBlank(resultFormatValue)) { + throw new CollectorException("Param 'resultFormatValue' is null or empty"); + } + if (StringUtils.isBlank(queryParams)) { + throw new CollectorException("Param 'queryParams' is null or empty"); + } + if (StringUtils.isBlank(entityXpath)) { + throw new CollectorException("Param 'entityXpath' is null or empty"); + } + + RestIterator it = new RestIterator( + getClientParams(), + baseUrl, + resumptionType, + resumptionParam, + resumptionXpath, + resultTotalXpath, + resultFormatParam, + resultFormatValue, + resultSizeParam, + resultSizeValue, + queryParams, + entityXpath, + authMethod, + authToken); + + return StreamSupport + .stream( + Spliterators.spliteratorUnknownSize(it, Spliterator.ORDERED), false); + } + + public HttpClientParams getClientParams() { + return clientParams; + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/rest/RestIterator.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/rest/RestIterator.java new file mode 100644 index 000000000..b728293d5 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/rest/RestIterator.java @@ -0,0 +1,442 @@ + +package eu.dnetlib.dhp.collection.plugin.rest; + +import java.io.InputStream; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Iterator; +import java.util.Queue; +import java.util.concurrent.PriorityBlockingQueue; + +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.*; + +import org.apache.commons.httpclient.HttpMethod; +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.apache.http.HttpHeaders; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import eu.dnetlib.dhp.collection.CollectorException; +import eu.dnetlib.dhp.collection.HttpClientParams; +import eu.dnetlib.dhp.collection.JsonUtils; + +/** + * log.debug(...) equal to log.trace(...) in the application-logs + *

+ * known bug: at resumptionType 'discover' if the (resultTotal % resultSizeValue) == 0 the collecting fails -> change the resultSizeValue + * + * @author Jochen Schirrwagen, Aenne Loehden, Andreas Czerniak + * @date 2020-04-09 + * + */ +public class RestIterator implements Iterator { + + private static final Log log = LogFactory.getLog(RestIterator.class); + + private HttpClientParams clientParams; + + private final String BASIC = "basic"; + + private JsonUtils jsonUtils; + + private String baseUrl; + private String resumptionType; + private String resumptionParam; + private String resultFormatValue; + private String queryParams; + private int resultSizeValue; + private int resumptionInt = 0; // integer resumption token (first record to harvest) + private int resultTotal = -1; + private String resumptionStr = Integer.toString(resumptionInt); // string resumption token (first record to harvest + // or token scanned from results) + private InputStream resultStream; + private Transformer transformer; + private XPath xpath; + private String query; + private XPathExpression xprResultTotalPath; + private XPathExpression xprResumptionPath; + private XPathExpression xprEntity; + private String queryFormat; + private String querySize; + private String authMethod; + private String authToken; + private final Queue recordQueue = new PriorityBlockingQueue(); + private int discoverResultSize = 0; + private int pagination = 1; + + /** + * RestIterator class + * + * compatible to version before 1.3.33 + * + * @param baseUrl + * @param resumptionType + * @param resumptionParam + * @param resumptionXpath + * @param resultTotalXpath + * @param resultFormatParam + * @param resultFormatValue + * @param resultSizeParam + * @param resultSizeValueStr + * @param queryParams + * @param entityXpath + */ + public RestIterator( + final HttpClientParams clientParams, + final String baseUrl, + final String resumptionType, + final String resumptionParam, + final String resumptionXpath, + final String resultTotalXpath, + final String resultFormatParam, + final String resultFormatValue, + final String resultSizeParam, + final String resultSizeValueStr, + final String queryParams, + final String entityXpath) { + this(clientParams, baseUrl, resumptionType, resumptionParam, resumptionXpath, resultTotalXpath, + resultFormatParam, resultFormatValue, resultSizeParam, resultSizeValueStr, queryParams, entityXpath, "", + ""); + } + + public RestIterator( + final HttpClientParams clientParams, + final String baseUrl, + final String resumptionType, + final String resumptionParam, + final String resumptionXpath, + final String resultTotalXpath, + final String resultFormatParam, + final String resultFormatValue, + final String resultSizeParam, + final String resultSizeValueStr, + final String queryParams, + final String entityXpath, + final String authMethod, + final String authToken, + final String resultOffsetParam) { + this(clientParams, baseUrl, resumptionType, resumptionParam, resumptionXpath, resultTotalXpath, + resultFormatParam, resultFormatValue, resultSizeParam, resultSizeValueStr, queryParams, entityXpath, "", + ""); + } + + /** RestIterator class + * compatible to version 1.3.33 + */ + public RestIterator( + final HttpClientParams clientParams, + final String baseUrl, + final String resumptionType, + final String resumptionParam, + final String resumptionXpath, + final String resultTotalXpath, + final String resultFormatParam, + final String resultFormatValue, + final String resultSizeParam, + final String resultSizeValueStr, + final String queryParams, + final String entityXpath, + final String authMethod, + final String authToken) { + this.clientParams = clientParams; + this.jsonUtils = new JsonUtils(); + this.baseUrl = baseUrl; + this.resumptionType = resumptionType; + this.resumptionParam = resumptionParam; + this.resultFormatValue = resultFormatValue; + this.queryParams = queryParams; + this.resultSizeValue = Integer.valueOf(resultSizeValueStr); + this.authMethod = authMethod; + this.authToken = authToken; + + queryFormat = StringUtils.isNotBlank(resultFormatParam) ? "&" + resultFormatParam + "=" + resultFormatValue + : ""; + querySize = StringUtils.isNotBlank(resultSizeParam) ? "&" + resultSizeParam + "=" + resultSizeValueStr : ""; + + try { + initXmlTransformation(resultTotalXpath, resumptionXpath, entityXpath); + } catch (Exception e) { + throw new IllegalStateException("xml transformation init failed: " + e.getMessage()); + } + initQueue(); + } + + private void initXmlTransformation(String resultTotalXpath, String resumptionXpath, String entityXpath) + throws TransformerConfigurationException, XPathExpressionException { + transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "3"); + xpath = XPathFactory.newInstance().newXPath(); + xprResultTotalPath = xpath.compile(resultTotalXpath); + xprResumptionPath = xpath.compile(StringUtils.isBlank(resumptionXpath) ? "/" : resumptionXpath); + xprEntity = xpath.compile(entityXpath); + } + + private void initQueue() { + query = baseUrl + "?" + queryParams + querySize + queryFormat; + } + + private void disconnect() { + // TODO close inputstream + } + + /* + * (non-Javadoc) + * @see java.util.Iterator#hasNext() + */ + @Override + public boolean hasNext() { + if (recordQueue.isEmpty() && query.isEmpty()) { + disconnect(); + return false; + } else { + return true; + } + } + + /* + * (non-Javadoc) + * @see java.util.Iterator#next() + */ + @Override + public String next() { + synchronized (recordQueue) { + while (recordQueue.isEmpty() && !query.isEmpty()) { + try { + log.debug("get Query: " + query); + query = downloadPage(query); + log.debug("next queryURL from downloadPage(): " + query); + } catch (CollectorException e) { + log.debug("CollectorPlugin.next()-Exception: " + e); + throw new RuntimeException(e); + } + } + return recordQueue.poll(); + } + } + + /* + * download page and return nextQuery + */ + private String downloadPage(String query) throws CollectorException { + String resultJson; + String resultXml = ""; + String emptyXml = resultXml + "<" + JsonUtils.wrapName + ">"; + Node resultNode = null; + NodeList nodeList = null; + InputStream theHttpInputStream; + + // check if cursor=* is initial set otherwise add it to the queryParam URL + if (resumptionType.equalsIgnoreCase("deep-cursor")) { + log.debug("check resumptionType deep-cursor and check cursor=*?" + query); + if (!query.contains("&cursor=")) { + query += "&cursor=*"; + } + } + + try { + URL qUrl = new URL(query); + log.debug("authMethod :" + authMethod); + if (this.authMethod == "bearer") { + log.trace("authMethod before inputStream: " + resultXml); + HttpURLConnection conn = (HttpURLConnection) qUrl.openConnection(); + conn.setRequestProperty(HttpHeaders.AUTHORIZATION, "Bearer " + authToken); + conn.setRequestProperty(HttpHeaders.CONTENT_TYPE, "application/json"); + conn.setRequestMethod("GET"); + theHttpInputStream = conn.getInputStream(); + } else if (BASIC.equalsIgnoreCase(this.authMethod)) { + log.trace("authMethod before inputStream: " + resultXml); + HttpURLConnection conn = (HttpURLConnection) qUrl.openConnection(); + conn.setRequestProperty(HttpHeaders.AUTHORIZATION, "Basic " + authToken); + conn.setRequestProperty(HttpHeaders.ACCEPT, "application/xml"); + conn.setRequestMethod("GET"); + theHttpInputStream = conn.getInputStream(); + } else { + theHttpInputStream = qUrl.openStream(); + } + + resultStream = theHttpInputStream; + if ("json".equalsIgnoreCase(resultFormatValue)) { + resultJson = IOUtils.toString(resultStream, "UTF-8"); + resultXml = jsonUtils.convertToXML(resultJson); + resultStream = IOUtils.toInputStream(resultXml, "UTF-8"); + } + + if (!(emptyXml).equalsIgnoreCase(resultXml)) { + resultNode = (Node) xpath.evaluate("/", new InputSource(resultStream), XPathConstants.NODE); + nodeList = (NodeList) xprEntity.evaluate(resultNode, XPathConstants.NODESET); + log.debug("nodeList.length: " + nodeList.getLength()); + for (int i = 0; i < nodeList.getLength(); i++) { + StringWriter sw = new StringWriter(); + transformer.transform(new DOMSource(nodeList.item(i)), new StreamResult(sw)); + recordQueue.add(sw.toString()); + } + } else { + log.info("resultXml is equal with emptyXml"); + } + + resumptionInt += resultSizeValue; + + String qUrlArgument = ""; + switch (resumptionType.toLowerCase()) { + case "scan": // read of resumptionToken , evaluate next results, e.g. OAI, iterate over items + resumptionStr = xprResumptionPath.evaluate(resultNode); + break; + + case "count": // begin at one step for all records, iterate over items + resumptionStr = Integer.toString(resumptionInt); + break; + + case "discover": // size of result items unknown, iterate over items (for openDOAR - 201808) + if (resultSizeValue < 2) { + throw new CollectorException("Mode: discover, Param 'resultSizeValue' is less than 2"); + } + qUrlArgument = qUrl.getQuery(); + String[] arrayQUrlArgument = qUrlArgument.split("&"); + int urlOldResumptionSize = 0; + for (String arrayUrlArgStr : arrayQUrlArgument) { + if (arrayUrlArgStr.startsWith(resumptionParam)) { + String[] resumptionKeyValue = arrayUrlArgStr.split("="); + if (isInteger(resumptionKeyValue[1])) { + urlOldResumptionSize = Integer.parseInt(resumptionKeyValue[1]); + log.debug("discover OldResumptionSize from Url (int): " + urlOldResumptionSize); + } else { + log.debug("discover OldResumptionSize from Url (str): " + resumptionKeyValue[1]); + } + } + } + + if (((emptyXml).equalsIgnoreCase(resultXml)) + || ((nodeList != null) && (nodeList.getLength() < resultSizeValue))) { + // resumptionStr = ""; + if (nodeList != null) { + discoverResultSize += nodeList.getLength(); + } + resultTotal = discoverResultSize; + } else { + resumptionStr = Integer.toString(resumptionInt); + resultTotal = resumptionInt + 1; + if (nodeList != null) { + discoverResultSize += nodeList.getLength(); + } + } + log.debug("discoverResultSize: " + discoverResultSize); + break; + + case "pagination": + case "page": // pagination, iterate over page numbers + pagination += 1; + if (nodeList != null) { + discoverResultSize += nodeList.getLength(); + } else { + resultTotal = discoverResultSize; + pagination = discoverResultSize; + } + resumptionInt = pagination; + resumptionStr = Integer.toString(resumptionInt); + break; + + case "deep-cursor": // size of result items unknown, iterate over items (for supporting deep cursor in + // solr) + // isn't relevant -- if (resultSizeValue < 2) {throw new CollectorServiceException("Mode: + // deep-cursor, Param 'resultSizeValue' is less than 2");} + + resumptionStr = encodeValue(xprResumptionPath.evaluate(resultNode)); + queryParams = queryParams.replace("&cursor=*", ""); + + // terminating if length of nodeList is 0 + if ((nodeList != null) && (nodeList.getLength() < discoverResultSize)) { + resumptionInt += (nodeList.getLength() + 1 - resultSizeValue); + } else { + resumptionInt += (nodeList.getLength() - resultSizeValue); // subtract the resultSizeValue + // because the iteration is over + // real length and the + // resultSizeValue is added before + // the switch() + } + + discoverResultSize = nodeList.getLength(); + + log + .debug( + "downloadPage().deep-cursor: resumptionStr=" + resumptionStr + " ; queryParams=" + + queryParams + " resumptionLengthIncreased: " + resumptionInt); + + break; + + default: // otherwise: abort + // resultTotal = resumptionInt; + break; + } + + } catch (Exception e) { + log.error(e); + throw new IllegalStateException("collection failed: " + e.getMessage()); + } + + try { + if (resultTotal == -1) { + resultTotal = Integer.parseInt(xprResultTotalPath.evaluate(resultNode)); + if (resumptionType.toLowerCase().equals("page") && !BASIC.equalsIgnoreCase(authMethod)) { + resultTotal += 1; + } // to correct the upper bound + log.info("resultTotal was -1 is now: " + resultTotal); + } + } catch (Exception e) { + log.error(e); + throw new IllegalStateException("downloadPage resultTotal couldn't parse: " + e.getMessage()); + } + log.debug("resultTotal: " + resultTotal); + log.debug("resInt: " + resumptionInt); + String nextQuery; + if (resumptionInt <= resultTotal) { + nextQuery = baseUrl + "?" + queryParams + querySize + "&" + resumptionParam + "=" + resumptionStr + + queryFormat; + } else { + nextQuery = ""; + // if (resumptionType.toLowerCase().equals("deep-cursor")) { resumptionInt -= 1; } // correct the + // resumptionInt and prevent a NullPointer Exception at mdStore + } + log.debug("nextQueryUrl: " + nextQuery); + return nextQuery; + } + + private boolean isInteger(String s) { + boolean isValidInteger = false; + try { + Integer.parseInt(s); + + // s is a valid integer + + isValidInteger = true; + } catch (NumberFormatException ex) { + // s is not an integer + } + + return isValidInteger; + } + + // Method to encode a string value using `UTF-8` encoding scheme + private String encodeValue(String value) { + try { + return URLEncoder.encode(value, StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException ex) { + throw new RuntimeException(ex.getCause()); + } + } + +} diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/plugin/rest/RestCollectorPluginTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/plugin/rest/RestCollectorPluginTest.java new file mode 100644 index 000000000..648ac85fb --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/plugin/rest/RestCollectorPluginTest.java @@ -0,0 +1,81 @@ +/** + * + */ + +package eu.dnetlib.dhp.collection.plugin.rest; + +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +import org.junit.jupiter.api.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import eu.dnetlib.dhp.aggregation.common.AggregatorReport; +import eu.dnetlib.dhp.collection.ApiDescriptor; +import eu.dnetlib.dhp.collection.CollectorException; +import eu.dnetlib.dhp.collection.HttpClientParams; + +/** + * @author js, Andreas Czerniak + * + */ +public class RestCollectorPluginTest { + + private static final Logger log = LoggerFactory.getLogger(RestCollectorPluginTest.class); + + private String baseUrl = "https://share.osf.io/api/v2/search/creativeworks/_search"; + private String resumptionType = "count"; + private String resumptionParam = "from"; + private String entityXpath = "//hits/hits"; + private String resumptionXpath = "//hits"; + private String resultTotalXpath = "//hits/total"; + private String resultFormatParam = "format"; + private String resultFormatValue = "json"; + private String resultSizeParam = "size"; + private String resultSizeValue = "10"; + // private String query = "q=%28sources%3ASocArXiv+AND+type%3Apreprint%29"; + private String query = "q=%28sources%3AengrXiv+AND+type%3Apreprint%29"; + // private String query = "=(sources:engrXiv AND type:preprint)"; + + private String protocolDescriptor = "rest_json2xml"; + private ApiDescriptor api = new ApiDescriptor(); + private RestCollectorPlugin rcp; + + @BeforeEach + public void setUp() { + HashMap params = new HashMap<>(); + params.put("resumptionType", resumptionType); + params.put("resumptionParam", resumptionParam); + params.put("resumptionXpath", resumptionXpath); + params.put("resultTotalXpath", resultTotalXpath); + params.put("resultFormatParam", resultFormatParam); + params.put("resultFormatValue", resultFormatValue); + params.put("resultSizeParam", resultSizeParam); + params.put("resultSizeValue", resultSizeValue); + params.put("queryParams", query); + params.put("entityXpath", entityXpath); + + api.setBaseUrl(baseUrl); + api.setParams(params); + + rcp = new RestCollectorPlugin(new HttpClientParams()); + } + + @Disabled + @Test + public void test() throws CollectorException { + AtomicInteger i = new AtomicInteger(0); + final Stream stream = rcp.collect(api, new AggregatorReport()); + + stream.limit(200).forEach(s -> { + Assertions.assertTrue(s.length() > 0); + i.incrementAndGet(); + log.info(s); + }); + + log.info("{}", i.intValue()); + Assertions.assertTrue(i.intValue() > 0); + } +} diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/plugin/rest/RestIteratorTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/plugin/rest/RestIteratorTest.java new file mode 100644 index 000000000..16604e0eb --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/collection/plugin/rest/RestIteratorTest.java @@ -0,0 +1,54 @@ +/** + * + */ + +package eu.dnetlib.dhp.collection.plugin.rest; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import eu.dnetlib.dhp.collection.HttpClientParams; + +/** + * + * @author js, Andreas Czerniak + * @date 2020-04-08 + */ +public class RestIteratorTest { + + private static final Logger log = LoggerFactory.getLogger(RestIteratorTest.class); + + private String baseUrl = "https://share.osf.io/api/v2/search/creativeworks/_search"; + private String resumptionType = "count"; + private String resumptionParam = "from"; + private String resumptionXpath = ""; + private String resultTotalXpath = "//hits/total"; + private String entityXpath = "//hits/hits"; + private String resultFormatParam = "format"; + private String resultFormatValue = "Json"; // Change from lowerCase to one UpperCase + private String resultSizeParam = "size"; + private String resultSizeValue = "10"; + private String authMethod = ""; + private String authToken = ""; + private String resultOffsetParam = "cursor"; + private String query = "q=%28sources%3ASocArXiv+AND+type%3Apreprint%29"; + + @Disabled + @Test + public void test() { + + HttpClientParams clientParams = new HttpClientParams(); + + final RestIterator iterator = new RestIterator(clientParams, baseUrl, resumptionType, resumptionParam, + resumptionXpath, resultTotalXpath, resultFormatParam, resultFormatValue, resultSizeParam, resultSizeValue, + query, entityXpath, authMethod, authToken, resultOffsetParam); + int i = 20; + while (iterator.hasNext() && i > 0) { + String result = iterator.next(); + + i--; + } + } +} diff --git a/pom.xml b/pom.xml index 45bb6bf78..5c45fad5f 100644 --- a/pom.xml +++ b/pom.xml @@ -461,6 +461,12 @@ ${apache.poi.version} + + org.json + json + 20180813 + + org.json4s json4s-jackson_2.11 From 76441f4edd64a8a6e3ad72600eec4d5e856855e6 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 9 Mar 2021 09:12:26 +0100 Subject: [PATCH 136/445] code formatting --- .../main/java/eu/dnetlib/dhp/schema/oaf/Relation.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Relation.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Relation.java index 8825d7137..adfc6af95 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Relation.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Relation.java @@ -1,8 +1,6 @@ package eu.dnetlib.dhp.schema.oaf; -import eu.dnetlib.dhp.schema.common.ModelSupport; - import static com.google.common.base.Preconditions.checkArgument; import java.text.ParseException; @@ -10,6 +8,8 @@ import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; +import eu.dnetlib.dhp.schema.common.ModelSupport; + /** * Relation models any edge between two nodes in the OpenAIRE graph. It has a source id and a target id pointing to * graph node identifiers and it is further characterised by the semantic of the link through the fields relType, @@ -137,7 +137,10 @@ public class Relation extends Oaf { try { setValidationDate(ModelSupport.oldest(getValidationDate(), r.getValidationDate())); } catch (ParseException e) { - throw new IllegalArgumentException(String.format("invalid validation date format in relation [s:%s, t:%s]: %s", getSource(), getTarget(), getValidationDate())); + throw new IllegalArgumentException(String + .format( + "invalid validation date format in relation [s:%s, t:%s]: %s", getSource(), getTarget(), + getValidationDate())); } super.mergeFrom(r); From f468c7f0d7af6b4560316e74c9e941589384763f Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 9 Mar 2021 09:12:41 +0100 Subject: [PATCH 137/445] merged from master --- .../raw/AbstractMdRecordToOafMapper.java | 22 ++++++++++++++++--- .../dnetlib/dhp/oa/graph/raw/MappersTest.java | 8 +++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java index 00a7f3a92..f03dcab7e 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java @@ -37,6 +37,8 @@ public abstract class AbstractMdRecordToOafMapper { protected static final Qualifier MAG_PID_TYPE = qualifier( "MAGIdentifier", "Microsoft Academic Graph Identifier", DNET_PID_TYPES, DNET_PID_TYPES); + protected static final String DEFAULT_TRUST_FOR_VALIDATED_RELS = "0.999"; + protected static final Map nsContext = new HashMap<>(); static { @@ -208,29 +210,41 @@ public abstract class AbstractMdRecordToOafMapper { final String originalId = ((Node) o).getText(); + final String validationdDate = ((Node) o).valueOf("@validationDate"); + if (StringUtils.isNotBlank(originalId)) { final String projectId = createOpenaireId(40, originalId, true); res .add( getRelation( - docId, projectId, RESULT_PROJECT, OUTCOME, IS_PRODUCED_BY, entity)); + docId, projectId, RESULT_PROJECT, OUTCOME, IS_PRODUCED_BY, entity, validationdDate)); res .add( getRelation( - projectId, docId, RESULT_PROJECT, OUTCOME, PRODUCES, entity)); + projectId, docId, RESULT_PROJECT, OUTCOME, PRODUCES, entity, validationdDate)); } } return res; } + protected Relation getRelation(final String source, + final String target, + final String relType, + final String subRelType, + final String relClass, + final OafEntity entity) { + return getRelation(source, target, relType, subRelType, relClass, entity, null); + } + protected Relation getRelation(final String source, final String target, final String relType, final String subRelType, final String relClass, - final OafEntity entity) { + final OafEntity entity, + final String validationDate) { final Relation rel = new Relation(); rel.setRelType(relType); rel.setSubRelType(subRelType); @@ -240,6 +254,8 @@ public abstract class AbstractMdRecordToOafMapper { rel.setCollectedfrom(entity.getCollectedfrom()); rel.setDataInfo(entity.getDataInfo()); rel.setLastupdatetimestamp(entity.getLastupdatetimestamp()); + rel.setValidated(StringUtils.isNotBlank(validationDate)); + rel.setValidationDate(StringUtils.isNotBlank(validationDate) ? validationDate : null); return rel; } diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java index baced2495..89ef7ed0e 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java @@ -71,8 +71,8 @@ public class MappersTest { assertValidId(p.getId()); - assertTrue(p.getOriginalId().size() == 1); - assertEquals("10.3897/oneeco.2.e13718", p.getOriginalId().get(0)); + assertEquals(2, p.getOriginalId().size()); + assertTrue(p.getOriginalId().contains("10.3897/oneeco.2.e13718")); assertValidId(p.getCollectedfrom().get(0).getKey()); assertTrue(StringUtils.isNotBlank(p.getTitle().get(0).getValue())); @@ -182,8 +182,8 @@ public class MappersTest { final Relation r2 = (Relation) list.get(2); assertValidId(d.getId()); - assertTrue(d.getOriginalId().size() == 1); - assertEquals("oai:zenodo.org:3234526", d.getOriginalId().get(0)); + assertEquals(2, d.getOriginalId().size()); + assertTrue(d.getOriginalId().contains("oai:zenodo.org:3234526")); assertValidId(d.getCollectedfrom().get(0).getKey()); assertTrue(StringUtils.isNotBlank(d.getTitle().get(0).getValue())); assertTrue(d.getAuthor().size() > 0); From a2169ccf07d558a6c7143c8e0b543dd76bde6f3c Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Tue, 12 Jan 2021 14:42:30 +0100 Subject: [PATCH 138/445] // implemented Ticket #6281 added pid to Instance in doiBoost --- .../main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala | 3 +++ .../src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala | 3 +++ .../src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala | 3 +++ 3 files changed, 9 insertions(+) diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala index 1e52e93c1..79aa7a586 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala @@ -172,6 +172,9 @@ case object Crossref2Oaf { instance.setLicense(l.head) + // Ticket #6281 added pid to Instance + instance.setPid(result.getPid.asScala.filter(p => p.getQualifier.getClassid.equalsIgnoreCase("doi")).asJava) + val has_review = (json \ "relation" \"has-review" \ "id") if(has_review != JNothing) { diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala index e0ff27421..910fad0e2 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala @@ -171,6 +171,9 @@ case object ConversionUtil { else i.setUrl(List(s"https://academic.microsoft.com/#/detail/${extractMagIdentifier(pub.getOriginalId.asScala)}").asJava) + // Ticket #6281 added pid to Instance + i.setPid(pub.getPid.asScala.filter(p => p.getQualifier.getClassid.equalsIgnoreCase("doi")).asJava) + i.setCollectedfrom(createMAGCollectedFrom()) pub.setInstance(List(i).asJava) pub diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala index c368c45a3..a26f57f0f 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala @@ -56,6 +56,9 @@ object UnpayWallToOAF { i.setAccessright(getOpenAccessQualifier()) i.setUrl(List(oaLocation.url.get).asJava) + // Ticket #6281 added pid to Instance + i.setPid(pub.getPid.asScala.filter(p => p.getQualifier.getClassid.equalsIgnoreCase("doi")).asJava) + if (oaLocation.license.isDefined) i.setLicense(asField(oaLocation.license.get)) pub.setInstance(List(i).asJava) From bbe1a7c69a84aa5f07693a9e2821159ae15347de Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Tue, 12 Jan 2021 14:45:17 +0100 Subject: [PATCH 139/445] [#6281 Provenance of product PIDs] Added PIDs to the Instance type in Scholexplorer Export --- .../src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala b/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala index 8043236e0..875c7cafd 100644 --- a/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala +++ b/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala @@ -343,6 +343,9 @@ object DLIToOAF { val instance_urls = if (fpids.head.length < 5) s"https://www.rcsb.org/structure/${fpids.head}" else s"https://dx.doi.org/${fpids.head}" val i: Instance = createInstance(instance_urls, firstInstanceOrNull(d.getInstance()), result.getDateofacceptance, true) + + // Ticket #6281 added pid to Instance + i.setPid(result.getPid) if (i != null) result.setInstance(List(i).asJava) From d525785497cf93625769d44bc0865714c92d8db7 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 12 Jan 2021 15:36:38 +0100 Subject: [PATCH 140/445] [#6282 open access status in the Graph] Result.Instance.accessRight defined with dedicated data type that includes the open access color. --- .../dhp/schema/oaf/CleaningFunctions.java | 40 +++++----------- .../dhp/schema/oaf/OafMapperUtils.java | 23 +++++++++ ...arator.java => AccessRightComparator.java} | 5 +- .../dhp/schema/common/ModelConstants.java | 3 +- .../dnetlib/dhp/schema/oaf/AccessRight.java | 48 +++++++++++++++++++ .../eu/dnetlib/dhp/schema/oaf/Instance.java | 6 +-- .../eu/dnetlib/dhp/schema/oaf/OAStatus.java | 13 +++++ .../eu/dnetlib/dhp/schema/oaf/Result.java | 5 +- .../dhp/schema/scholexplorer/OafUtils.scala | 11 ++++- .../migration/ProtoConverter.java | 13 ++++- .../doiboost/DoiBoostMappingUtil.scala | 36 +++++--------- .../doiboost/crossref/Crossref2Oaf.scala | 8 ++-- .../orcidnodoi/oaf/PublicationToOaf.java | 44 ++++++++++++----- .../dhp/oa/graph/clean/CleaningRuleMap.java | 2 + .../raw/AbstractMdRecordToOafMapper.java | 24 +++++++++- .../dhp/oa/graph/raw/OafToOafMapper.java | 2 +- .../dhp/oa/graph/raw/OdfToOafMapper.java | 27 ++++++----- .../java/eu/dnetlib/dhp/export/DLIToOAF.scala | 6 +-- 18 files changed, 217 insertions(+), 99 deletions(-) rename dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/{LicenseComparator.java => AccessRightComparator.java} (87%) create mode 100644 dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/AccessRight.java create mode 100644 dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/OAStatus.java diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index da4ed63a9..d21a58395 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -84,7 +84,7 @@ public class CleaningFunctions { } else if (value instanceof Organization) { Organization o = (Organization) value; if (Objects.isNull(o.getCountry()) || StringUtils.isBlank(o.getCountry().getClassid())) { - o.setCountry(qualifier("UNKNOWN", "Unknown", ModelConstants.DNET_COUNTRY_TYPE)); + o.setCountry(ModelConstants.UNKNOWN_COUNTRY); } } else if (value instanceof Relation) { // nothing to clean here @@ -152,12 +152,14 @@ public class CleaningFunctions { if (Objects.isNull(r.getResourcetype()) || StringUtils.isBlank(r.getResourcetype().getClassid())) { r .setResourcetype( - qualifier("UNKNOWN", "Unknown", ModelConstants.DNET_DATA_CITE_RESOURCE)); + qualifier(ModelConstants.UNKNOWN, "Unknown", ModelConstants.DNET_DATA_CITE_RESOURCE)); } if (Objects.nonNull(r.getInstance())) { for (Instance i : r.getInstance()) { if (Objects.isNull(i.getAccessright()) || StringUtils.isBlank(i.getAccessright().getClassid())) { - i.setAccessright(qualifier("UNKNOWN", "not available", ModelConstants.DNET_ACCESS_MODES)); + i + .setAccessright( + accessRight(ModelConstants.UNKNOWN, "not available", ModelConstants.DNET_ACCESS_MODES)); } if (Objects.isNull(i.getHostedby()) || StringUtils.isBlank(i.getHostedby().getKey())) { i.setHostedby(ModelConstants.UNKNOWN_REPOSITORY); @@ -203,6 +205,7 @@ public class CleaningFunctions { p.setValue(p.getValue().trim().replaceAll(ORCID_PREFIX_REGEX, "")); return p; }) + .filter(p -> StringUtils.isNotBlank(p.getValue())) .collect( Collectors .toMap( @@ -248,35 +251,18 @@ public class CleaningFunctions { } } + private static AccessRight accessRight(String classid, String classname, String scheme) { + return OafMapperUtils + .accessRight( + classid, classname, scheme, scheme); + } + private static Qualifier qualifier(String classid, String classname, String scheme) { return OafMapperUtils .qualifier( classid, classname, scheme, scheme); } - /** - * Utility method that filter PID values on a per-type basis. - * @param pid the PID whose value will be checked. - * @return true the PID containing the normalised value. - */ - private static boolean filterPid(StructuredProperty pid) { - String value = Optional - .ofNullable(pid.getValue()) - .map(s -> StringUtils.replaceAll(s, "\\s", "")) - .orElse(""); - if (StringUtils.isBlank(value)) { - return false; - } - switch (pid.getQualifier().getClassid()) { - - // TODO add cleaning for more PID types as needed - case "doi": - return value.startsWith("10."); - default: - return true; - } - } - /** * Utility method that normalises PID values on a per-type basis. * @param pid the PID whose value will be normalised. @@ -291,7 +277,7 @@ public class CleaningFunctions { // TODO add cleaning for more PID types as needed case "doi": - pid.setValue(value.toLowerCase().replaceAll(DOI_PREFIX_REGEX, "10.")); + pid.setValue(value.toLowerCase().replaceAll(DOI_URL_PREFIX_REGEX, "")); break; } return pid; diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java index 5d6e7565d..66f843056 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java @@ -105,6 +105,29 @@ public class OafMapperUtils { return qualifier("UNKNOWN", "Unknown", schemeid, schemename); } + public static AccessRight accessRight( + final String classid, + final String classname, + final String schemeid, + final String schemename) { + return accessRight(classid, classname, schemeid, schemename, null); + } + + public static AccessRight accessRight( + final String classid, + final String classname, + final String schemeid, + final String schemename, + final OAStatus oaStatus) { + final AccessRight accessRight = new AccessRight(); + accessRight.setClassid(classid); + accessRight.setClassname(classname); + accessRight.setSchemeid(schemeid); + accessRight.setSchemename(schemename); + accessRight.setOaStatus(oaStatus); + return accessRight; + } + public static Qualifier qualifier( final String classid, final String classname, diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/LicenseComparator.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/AccessRightComparator.java similarity index 87% rename from dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/LicenseComparator.java rename to dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/AccessRightComparator.java index db523ad1a..6116bc479 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/LicenseComparator.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/AccessRightComparator.java @@ -3,12 +3,13 @@ package eu.dnetlib.dhp.schema.common; import java.util.Comparator; +import eu.dnetlib.dhp.schema.oaf.AccessRight; import eu.dnetlib.dhp.schema.oaf.Qualifier; -public class LicenseComparator implements Comparator { +public class AccessRightComparator implements Comparator { @Override - public int compare(Qualifier left, Qualifier right) { + public int compare(T left, T right) { if (left == null && right == null) return 0; diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java index 10b2c7418..143340b07 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java @@ -20,7 +20,8 @@ public class ModelConstants { public static final String DNET_ACCESS_MODES = "dnet:access_modes"; public static final String DNET_LANGUAGES = "dnet:languages"; public static final String DNET_PID_TYPES = "dnet:pid_types"; - public static final String DNET_DATA_CITE_DATE = "dnet:dataCite_date"; + public static final String DNET_DATACITE_DATE = "dnet:dataCite_date"; + public static final String DNET_DATACITE_TITLE = "dnet:dataCite_title"; public static final String DNET_DATA_CITE_RESOURCE = "dnet:dataCite_resource"; public static final String DNET_PROVENANCE_ACTIONS = "dnet:provenanceActions"; public static final String DNET_COUNTRY_TYPE = "dnet:countries"; diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/AccessRight.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/AccessRight.java new file mode 100644 index 000000000..ef67f7d4b --- /dev/null +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/AccessRight.java @@ -0,0 +1,48 @@ + +package eu.dnetlib.dhp.schema.oaf; + +import java.util.Optional; + +/** + * This class models the access rights of research products. + */ +public class AccessRight extends Qualifier { + + private OAStatus oaStatus; + + public OAStatus getOaStatus() { + return oaStatus; + } + + public void setOaStatus(OAStatus oaStatus) { + this.oaStatus = oaStatus; + } + + public String toComparableString() { + String s = super.toComparableString(); + return Optional + .ofNullable(getOaStatus()) + .map(x -> s + "::" + x.toString()) + .orElse(s); + } + + @Override + public int hashCode() { + return toComparableString().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + + Qualifier other = (Qualifier) obj; + + return toComparableString().equals(other.toComparableString()); + } + +} diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Instance.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Instance.java index 29d495261..edf424aa8 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Instance.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Instance.java @@ -8,7 +8,7 @@ public class Instance implements Serializable { private Field license; - private Qualifier accessright; + private AccessRight accessright; private Qualifier instancetype; @@ -41,11 +41,11 @@ public class Instance implements Serializable { this.license = license; } - public Qualifier getAccessright() { + public AccessRight getAccessright() { return accessright; } - public void setAccessright(Qualifier accessright) { + public void setAccessright(AccessRight accessright) { this.accessright = accessright; } diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/OAStatus.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/OAStatus.java new file mode 100644 index 000000000..1d1cbf0aa --- /dev/null +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/OAStatus.java @@ -0,0 +1,13 @@ + +package eu.dnetlib.dhp.schema.oaf; + +/** + * This Enum models the OpenAccess status, currently including only the values from Unpaywall + * + * https://support.unpaywall.org/support/solutions/articles/44001777288-what-do-the-types-of-oa-status-green-gold-hybrid-and-bronze-mean- + */ +public enum OAStatus { + + gold, green, hybrid, bronze + +} diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Result.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Result.java index 845c4c982..945ebad50 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Result.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Result.java @@ -2,12 +2,11 @@ package eu.dnetlib.dhp.schema.oaf; import java.io.Serializable; -import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; -import eu.dnetlib.dhp.schema.common.LicenseComparator; +import eu.dnetlib.dhp.schema.common.AccessRightComparator; public class Result extends OafEntity implements Serializable { @@ -248,7 +247,7 @@ public class Result extends OafEntity implements Serializable { instance = mergeLists(instance, r.getInstance()); if (r.getBestaccessright() != null - && new LicenseComparator().compare(r.getBestaccessright(), bestaccessright) < 0) + && new AccessRightComparator().compare(r.getBestaccessright(), bestaccessright) < 0) bestaccessright = r.getBestaccessright(); if (r.getResulttype() != null && compareTrust(this, r) < 0) diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/scholexplorer/OafUtils.scala b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/scholexplorer/OafUtils.scala index 27eec77fa..371efc3a7 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/scholexplorer/OafUtils.scala +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/scholexplorer/OafUtils.scala @@ -1,6 +1,6 @@ package eu.dnetlib.dhp.schema.scholexplorer -import eu.dnetlib.dhp.schema.oaf.{DataInfo, Field, KeyValue, Qualifier, StructuredProperty} +import eu.dnetlib.dhp.schema.oaf.{AccessRight, DataInfo, Field, KeyValue, Qualifier, StructuredProperty} object OafUtils { @@ -39,6 +39,15 @@ object OafUtils { q } + def createAccessRight(classId: String, className: String, schemeId: String, schemeName: String): AccessRight = { + val accessRight: AccessRight = new AccessRight + accessRight.setClassid(classId) + accessRight.setClassname(className) + accessRight.setSchemeid(schemeId) + accessRight.setSchemename(schemeName) + accessRight + } + def asField[T](value: T): Field[T] = { val tmp = new Field[T] diff --git a/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/migration/ProtoConverter.java b/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/migration/ProtoConverter.java index 8ea877aec..5aeb38bb5 100644 --- a/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/migration/ProtoConverter.java +++ b/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/migration/ProtoConverter.java @@ -82,7 +82,7 @@ public class ProtoConverter implements Serializable { private static Instance convertInstance(ResultProtos.Result.Instance ri) { final Instance i = new Instance(); - i.setAccessright(mapQualifier(ri.getAccessright())); + i.setAccessright(mapAccessRight(ri.getAccessright())); i.setCollectedfrom(mapKV(ri.getCollectedfrom())); i.setDateofacceptance(mapStringField(ri.getDateofacceptance())); i.setDistributionlocation(ri.getDistributionlocation()); @@ -510,7 +510,7 @@ public class ProtoConverter implements Serializable { .map(i -> i.getAccessright()) .min(new LicenseComparator()); - final Qualifier rights = min.isPresent() ? mapQualifier(min.get()) : new Qualifier(); + final Qualifier rights = min.isPresent() ? mapAccessRight(min.get()) : new Qualifier(); if (StringUtils.isBlank(rights.getClassid())) { rights.setClassid(UNKNOWN); @@ -579,6 +579,15 @@ public class ProtoConverter implements Serializable { return qualifier; } + public static AccessRight mapAccessRight(FieldTypeProtos.Qualifier q) { + final AccessRight accessRight = new AccessRight(); + accessRight.setClassid(q.getClassid()); + accessRight.setClassname(q.getClassname()); + accessRight.setSchemeid(q.getSchemeid()); + accessRight.setSchemename(q.getSchemename()); + return accessRight; + } + public static Country mapQualifierAsCountry(FieldTypeProtos.Qualifier q) { final Country c = new Country(); c.setClassid(q.getClassid()); diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala index 683986de2..efe8453a2 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala @@ -1,10 +1,11 @@ package eu.dnetlib.doiboost import eu.dnetlib.dhp.schema.action.AtomicAction -import eu.dnetlib.dhp.schema.oaf.{DataInfo, Dataset, Field, Instance, KeyValue, Oaf, Organization, Publication, Qualifier, Relation, Result, StructuredProperty} +import eu.dnetlib.dhp.schema.oaf.{AccessRight, DataInfo, Dataset, Field, Instance, KeyValue, Oaf, Organization, Publication, Qualifier, Relation, Result, StructuredProperty} import eu.dnetlib.dhp.utils.DHPUtils import org.apache.commons.lang3.StringUtils import com.fasterxml.jackson.databind.ObjectMapper +import eu.dnetlib.dhp.schema.scholexplorer.OafUtils import org.json4s import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods.parse @@ -125,13 +126,12 @@ object DoiBoostMappingUtil { } - def getOpenAccessQualifier():Qualifier = { - createQualifier("OPEN","Open Access","dnet:access_modes", "dnet:access_modes") - + def getOpenAccessQualifier():AccessRight = { + OafUtils.createAccessRight("OPEN","Open Access","dnet:access_modes", "dnet:access_modes") } - def getRestrictedQualifier():Qualifier = { - createQualifier("RESTRICTED","Restricted","dnet:access_modes", "dnet:access_modes") + def getRestrictedQualifier():AccessRight = { + OafUtils.createAccessRight("RESTRICTED","Restricted","dnet:access_modes", "dnet:access_modes") } @@ -260,7 +260,7 @@ object DoiBoostMappingUtil { di.setInferred(false) di.setInvisible(false) di.setTrust(trust) - di.setProvenanceaction(createQualifier("sysimport:actionset", "dnet:provenanceActions")) + di.setProvenanceaction(OafUtils.createQualifier("sysimport:actionset", "dnet:provenanceActions")) di } @@ -268,7 +268,7 @@ object DoiBoostMappingUtil { def createSP(value: String, classId: String,className:String, schemeId: String, schemeName:String): StructuredProperty = { val sp = new StructuredProperty - sp.setQualifier(createQualifier(classId,className, schemeId, schemeName)) + sp.setQualifier(OafUtils.createQualifier(classId,className, schemeId, schemeName)) sp.setValue(value) sp @@ -278,7 +278,7 @@ object DoiBoostMappingUtil { def createSP(value: String, classId: String,className:String, schemeId: String, schemeName:String, dataInfo: DataInfo): StructuredProperty = { val sp = new StructuredProperty - sp.setQualifier(createQualifier(classId,className, schemeId, schemeName)) + sp.setQualifier(OafUtils.createQualifier(classId,className, schemeId, schemeName)) sp.setValue(value) sp.setDataInfo(dataInfo) sp @@ -287,7 +287,7 @@ object DoiBoostMappingUtil { def createSP(value: String, classId: String, schemeId: String): StructuredProperty = { val sp = new StructuredProperty - sp.setQualifier(createQualifier(classId, schemeId)) + sp.setQualifier(OafUtils.createQualifier(classId, schemeId)) sp.setValue(value) sp @@ -297,7 +297,7 @@ object DoiBoostMappingUtil { def createSP(value: String, classId: String, schemeId: String, dataInfo: DataInfo): StructuredProperty = { val sp = new StructuredProperty - sp.setQualifier(createQualifier(classId, schemeId)) + sp.setQualifier(OafUtils.createQualifier(classId, schemeId)) sp.setValue(value) sp.setDataInfo(dataInfo) sp @@ -350,20 +350,6 @@ object DoiBoostMappingUtil { } - def createQualifier(clsName: String, clsValue: String, schName: String, schValue: String): Qualifier = { - val q = new Qualifier - q.setClassid(clsName) - q.setClassname(clsValue) - q.setSchemeid(schName) - q.setSchemename(schValue) - q - } - - def createQualifier(cls: String, sch: String): Qualifier = { - createQualifier(cls, cls, sch, sch) - } - - def asField[T](value: T): Field[T] = { val tmp = new Field[T] tmp.setValue(value) diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala index 79aa7a586..9251bba0e 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala @@ -15,6 +15,8 @@ import scala.collection.JavaConverters._ import scala.collection.mutable import scala.util.matching.Regex +import eu.dnetlib.dhp.schema.scholexplorer.OafUtils; + case class CrossrefDT(doi: String, json:String, timestamp: Long) {} case class mappingAffiliation(name: String) {} @@ -179,14 +181,14 @@ case object Crossref2Oaf { if(has_review != JNothing) { instance.setRefereed( - createQualifier("0001", "peerReviewed", "dnet:review_levels", "dnet:review_levels")) + OafUtils.createQualifier("0001", "peerReviewed", "dnet:review_levels", "dnet:review_levels")) } instance.setAccessright(getRestrictedQualifier()) result.setInstance(List(instance).asJava) - instance.setInstancetype(createQualifier(cobjCategory.substring(0, 4), cobjCategory.substring(5), "dnet:publication_resource", "dnet:publication_resource")) - result.setResourcetype(createQualifier(cobjCategory.substring(0, 4),"dnet:dataCite_resource")) + instance.setInstancetype(OafUtils.createQualifier(cobjCategory.substring(0, 4), cobjCategory.substring(5), "dnet:publication_resource", "dnet:publication_resource")) + result.setResourcetype(OafUtils.createQualifier(cobjCategory.substring(0, 4),"dnet:dataCite_resource")) instance.setCollectedfrom(createCrossrefCollectedFrom()) if (StringUtils.isNotBlank(issuedDate)) { diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java index 1aed66dfd..8ba397048 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java @@ -18,6 +18,7 @@ import com.google.gson.*; import eu.dnetlib.dhp.common.PacePerson; import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.*; +import eu.dnetlib.dhp.schema.scholexplorer.OafUtils; import eu.dnetlib.dhp.utils.DHPUtils; import eu.dnetlib.doiboost.orcidnodoi.util.DumpToActionsUtility; import eu.dnetlib.doiboost.orcidnodoi.util.Pair; @@ -102,8 +103,6 @@ public class PublicationToOaf implements Serializable { } } - public static final String PID_TYPES = "dnet:pid_types"; - public Oaf generatePublicationActionsFromJson(final String json) { try { if (parsedPublications != null) { @@ -138,8 +137,8 @@ public class PublicationToOaf implements Serializable { mapQualifier( "sysimport:actionset:orcidworks-no-doi", "sysimport:actionset:orcidworks-no-doi", - "dnet:provenanceActions", - "dnet:provenanceActions")); + ModelConstants.DNET_PROVENANCE_ACTIONS, + ModelConstants.DNET_PROVENANCE_ACTIONS)); publication.setDataInfo(dataInfo); publication.setLastupdatetimestamp(new Date().getTime()); @@ -159,7 +158,9 @@ public class PublicationToOaf implements Serializable { publication .getExternalReference() .add( - convertExtRef(extId, classid, classname, "dnet:pid_types", "dnet:pid_types")); + convertExtRef( + extId, classid, classname, ModelConstants.DNET_PID_TYPES, + ModelConstants.DNET_PID_TYPES)); } }); @@ -182,7 +183,8 @@ public class PublicationToOaf implements Serializable { } return null; } - Qualifier q = mapQualifier("main title", "main title", "dnet:dataCite_title", "dnet:dataCite_title"); + Qualifier q = mapQualifier( + "main title", "main title", ModelConstants.DNET_DATACITE_TITLE, ModelConstants.DNET_DATACITE_TITLE); publication .setTitle( titles @@ -214,7 +216,10 @@ public class PublicationToOaf implements Serializable { final String type = getStringValue(rootElement, "type"); String cobjValue = ""; if (StringUtils.isNotBlank(type)) { - publication.setResourcetype(mapQualifier(type, type, "dnet:dataCite_resource", "dnet:dataCite_resource")); + publication + .setResourcetype( + mapQualifier( + type, type, ModelConstants.DNET_DATA_CITE_RESOURCE, ModelConstants.DNET_DATA_CITE_RESOURCE)); final String typeValue = typologiesMapping.get(type).get("value"); cobjValue = typologiesMapping.get(type).get("cobj"); @@ -239,12 +244,21 @@ public class PublicationToOaf implements Serializable { instance.setCollectedfrom(createCollectedFrom()); // Adding accessright - instance.setAccessright(mapQualifier("UNKNOWN", "UNKNOWN", "dnet:access_modes", "dnet:access_modes")); + instance + .setAccessright( + OafUtils + .createAccessRight( + ModelConstants.UNKNOWN, + ModelConstants.UNKNOWN, + ModelConstants.DNET_ACCESS_MODES, + ModelConstants.DNET_ACCESS_MODES)); // Adding type instance .setInstancetype( - mapQualifier(cobjValue, typeValue, "dnet:publication_resource", "dnet:publication_resource")); + mapQualifier( + cobjValue, typeValue, ModelConstants.DNET_PUBLICATION_RESOURCE, + ModelConstants.DNET_PUBLICATION_RESOURCE)); publication.setInstance(Arrays.asList(instance)); } else { @@ -266,7 +280,10 @@ public class PublicationToOaf implements Serializable { } String classValue = getDefaultResulttype(cobjValue); publication - .setResulttype(mapQualifier(classValue, classValue, "dnet:result_typologies", "dnet:result_typologies")); + .setResulttype( + mapQualifier( + classValue, classValue, ModelConstants.DNET_RESULT_TYPOLOGIES, + ModelConstants.DNET_RESULT_TYPOLOGIES)); if (enrichedPublications != null) { enrichedPublications.add(1); } @@ -373,7 +390,8 @@ public class PublicationToOaf implements Serializable { if (addToDateOfAcceptance) { publication.setDateofacceptance(mapStringField(pubDate, null)); } - Qualifier q = mapQualifier(dictionaryKey, dictionaryKey, "dnet:dataCite_date", "dnet:dataCite_date"); + Qualifier q = mapQualifier( + dictionaryKey, dictionaryKey, ModelConstants.DNET_DATACITE_DATE, ModelConstants.DNET_DATACITE_DATE); publication .setRelevantdate( Arrays @@ -535,8 +553,8 @@ public class PublicationToOaf implements Serializable { mapQualifier( "sysimport:crosswalk:entityregistry", "Harvested", - "dnet:provenanceActions", - "dnet:provenanceActions")); + ModelConstants.DNET_PROVENANCE_ACTIONS, + ModelConstants.DNET_PROVENANCE_ACTIONS)); sp.setDataInfo(dataInfo); return sp; } diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleaningRuleMap.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleaningRuleMap.java index d2d4e118f..08695ec67 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleaningRuleMap.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleaningRuleMap.java @@ -9,6 +9,7 @@ import org.apache.commons.lang3.StringUtils; import eu.dnetlib.dhp.common.FunctionalInterfaceSupport.SerializableConsumer; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; import eu.dnetlib.dhp.schema.common.ModelConstants; +import eu.dnetlib.dhp.schema.oaf.AccessRight; import eu.dnetlib.dhp.schema.oaf.Country; import eu.dnetlib.dhp.schema.oaf.Qualifier; @@ -22,6 +23,7 @@ public class CleaningRuleMap extends HashMap public static CleaningRuleMap create(VocabularyGroup vocabularies) { CleaningRuleMap mapping = new CleaningRuleMap(); mapping.put(Qualifier.class, o -> cleanQualifier(vocabularies, (Qualifier) o)); + mapping.put(AccessRight.class, o -> cleanQualifier(vocabularies, (AccessRight) o)); mapping.put(Country.class, o -> { final Country c = (Country) o; if (StringUtils.isBlank(c.getSchemeid())) { diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java index f03dcab7e..561c6a6b8 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java @@ -4,7 +4,13 @@ package eu.dnetlib.dhp.oa.graph.raw; import static eu.dnetlib.dhp.schema.common.ModelConstants.*; import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.*; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.dom4j.Document; @@ -15,7 +21,7 @@ import org.dom4j.Node; import com.google.common.collect.Lists; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; -import eu.dnetlib.dhp.schema.common.LicenseComparator; +import eu.dnetlib.dhp.schema.common.AccessRightComparator; import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory; @@ -418,6 +424,20 @@ public abstract class AbstractMdRecordToOafMapper { } + protected AccessRight prepareAccessRight(final Node node, final String xpath, final String schemeId) { + Qualifier qualifier = prepareQualifier(node.valueOf(xpath).trim(), schemeId); + AccessRight accessRight = new AccessRight(); + accessRight.setClassid(qualifier.getClassid()); + accessRight.setClassname(qualifier.getClassname()); + accessRight.setSchemeid(qualifier.getSchemeid()); + accessRight.setSchemename(qualifier.getSchemename()); + + // TODO set the OAStatus + // accessRight.setOaStatus(...); + + return accessRight; + } + protected Qualifier prepareQualifier(final Node node, final String xpath, final String schemeId) { return prepareQualifier(node.valueOf(xpath).trim(), schemeId); } diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java index 50208a079..48219be97 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java @@ -128,7 +128,7 @@ public class OafToOafMapper extends AbstractMdRecordToOafMapper { instance.setDateofacceptance(field(doc.valueOf("//oaf:dateAccepted"), info)); instance.setDistributionlocation(doc.valueOf("//oaf:distributionlocation")); instance - .setAccessright(prepareQualifier(doc, "//oaf:accessrights", DNET_ACCESS_MODES)); + .setAccessright(prepareAccessRight(doc, "//oaf:accessrights", DNET_ACCESS_MODES)); instance.setLicense(field(doc.valueOf("//oaf:license"), info)); instance.setRefereed(prepareQualifier(doc, "//oaf:refereed", DNET_REVIEW_LEVELS)); instance diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java index cddd00ad7..9d0d2368a 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java @@ -102,10 +102,11 @@ public class OdfToOafMapper extends AbstractMdRecordToOafMapper { .setInstancetype(prepareQualifier(doc, "//dr:CobjCategory", DNET_PUBLICATION_RESOURCE)); instance.setCollectedfrom(collectedfrom); instance.setHostedby(hostedby); + instance.setPid(prepareResultPids(doc, info)); instance.setDateofacceptance(field(doc.valueOf("//oaf:dateAccepted"), info)); instance.setDistributionlocation(doc.valueOf("//oaf:distributionlocation")); instance - .setAccessright(prepareQualifier(doc, "//oaf:accessrights", DNET_ACCESS_MODES)); + .setAccessright(prepareAccessRight(doc, "//oaf:accessrights", DNET_ACCESS_MODES)); instance.setLicense(field(doc.valueOf("//oaf:license"), info)); instance.setRefereed(prepareQualifier(doc, "//oaf:refereed", DNET_REVIEW_LEVELS)); instance.setProcessingchargeamount(field(doc.valueOf("//oaf:processingchargeamount"), info)); @@ -150,14 +151,20 @@ public class OdfToOafMapper extends AbstractMdRecordToOafMapper { for (final Object o : doc.selectNodes("//datacite:date")) { final String dateType = ((Node) o).valueOf("@dateType"); if (StringUtils.isBlank(dateType) - && !dateType.equalsIgnoreCase("Accepted") - && !dateType.equalsIgnoreCase("Issued") - && !dateType.equalsIgnoreCase("Updated") - && !dateType.equalsIgnoreCase("Available")) { + || (!dateType.equalsIgnoreCase("Accepted") + && !dateType.equalsIgnoreCase("Issued") + && !dateType.equalsIgnoreCase("Updated") + && !dateType.equalsIgnoreCase("Available"))) { res .add( structuredProperty( - ((Node) o).getText(), "UNKNOWN", "UNKNOWN", DNET_DATA_CITE_DATE, DNET_DATA_CITE_DATE, + ((Node) o).getText(), "UNKNOWN", "UNKNOWN", DNET_DATACITE_DATE, DNET_DATACITE_DATE, + info)); + } else { + res + .add( + structuredProperty( + ((Node) o).getText(), dateType, dateType, DNET_DATACITE_DATE, DNET_DATACITE_DATE, info)); } } @@ -186,13 +193,7 @@ public class OdfToOafMapper extends AbstractMdRecordToOafMapper { @Override protected List> prepareDescriptions(final Document doc, final DataInfo info) { - return prepareListFields(doc, "//datacite:description[@descriptionType='Abstract']", info) - .stream() - .map(d -> { - d.setValue(StringUtils.left(d.getValue(), ModelHardLimits.MAX_ABSTRACT_LENGTH)); - return d; - }) - .collect(Collectors.toList()); + return prepareListFields(doc, "//datacite:description[@descriptionType='Abstract']", info); } @Override diff --git a/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala b/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala index 875c7cafd..607c4fa45 100644 --- a/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala +++ b/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala @@ -258,7 +258,7 @@ object DLIToOAF { result.setDateofacceptance(asField(inputPublication.getRelevantdate.get(0).getValue)) result.setPublisher(inputPublication.getPublisher) result.setSource(inputPublication.getSource) - result.setBestaccessright(createQualifier("UNKNOWN", "not available", "dnet:access_modes", "dnet:access_modes")) + result.setBestaccessright(createAccessRight("UNKNOWN", "not available", "dnet:access_modes", "dnet:access_modes")) val dois = result.getPid.asScala.filter(p => "doi".equalsIgnoreCase(p.getQualifier.getClassname)).map(p => p.getValue) if (dois.isEmpty) @@ -337,7 +337,7 @@ object DLIToOAF { result.setDateofacceptance(asField(d.getRelevantdate.get(0).getValue)) result.setPublisher(d.getPublisher) result.setSource(d.getSource) - result.setBestaccessright(createQualifier("UNKNOWN", "not available", "dnet:access_modes", "dnet:access_modes")) + result.setBestaccessright(createAccessRight("UNKNOWN", "not available", "dnet:access_modes", "dnet:access_modes")) val instance_urls = if (fpids.head.length < 5) s"https://www.rcsb.org/structure/${fpids.head}" else s"https://dx.doi.org/${fpids.head}" @@ -373,7 +373,7 @@ object DLIToOAF { if (originalInstance != null && originalInstance.getHostedby != null) i.setHostedby(originalInstance.getHostedby) - i.setAccessright(createQualifier("UNKNOWN", "not available", "dnet:access_modes", "dnet:access_modes")) + i.setAccessright(createAccessRight("UNKNOWN", "not available", "dnet:access_modes", "dnet:access_modes")) i.setDateofacceptance(doa) i From 59532b0919acd5682c0b249280c8d2a7448c742f Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 11 Jan 2021 17:15:18 +0100 Subject: [PATCH 141/445] [#6281 Provenance of product PIDs] Added PIDs to the Instance type; extended mapping for OAF/ODF records --- .../main/java/eu/dnetlib/dhp/schema/oaf/Instance.java | 10 ++++++++++ .../eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java | 1 + .../java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java | 8 ++++++++ 3 files changed, 19 insertions(+) diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Instance.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Instance.java index edf424aa8..aae5695df 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Instance.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Instance.java @@ -21,6 +21,8 @@ public class Instance implements Serializable { private KeyValue collectedfrom; + private List pid; + private Field dateofacceptance; // ( article | book ) processing charges. Defined here to cope with possible wrongly typed @@ -89,6 +91,14 @@ public class Instance implements Serializable { this.collectedfrom = collectedfrom; } + public List getPid() { + return pid; + } + + public void setPid(List pid) { + this.pid = pid; + } + public Field getDateofacceptance() { return dateofacceptance; } diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java index 48219be97..ccb3d6caf 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java @@ -125,6 +125,7 @@ public class OafToOafMapper extends AbstractMdRecordToOafMapper { .setInstancetype(prepareQualifier(doc, "//dr:CobjCategory", DNET_PUBLICATION_RESOURCE)); instance.setCollectedfrom(collectedfrom); instance.setHostedby(hostedby); + instance.setPid(prepareResultPids(doc, info)); instance.setDateofacceptance(field(doc.valueOf("//oaf:dateAccepted"), info)); instance.setDistributionlocation(doc.valueOf("//oaf:distributionlocation")); instance diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java index 89ef7ed0e..37d5bf33b 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java @@ -122,6 +122,10 @@ public class MappersTest { assertEquals("OPEN", i.getAccessright().getClassid()); }); assertEquals("0001", p.getInstance().get(0).getRefereed().getClassid()); + assertNotNull(p.getInstance().get(0).getPid()); + assertTrue(p.getInstance().get(0).getPid().size() == 1); + assertEquals("doi", p.getInstance().get(0).getPid().get(0).getQualifier().getClassid()); + assertEquals("10.3897/oneeco.2.e13718", p.getInstance().get(0).getPid().get(0).getValue()); assertNotNull(p.getBestaccessright()); assertEquals("OPEN", p.getBestaccessright().getClassid()); @@ -234,6 +238,10 @@ public class MappersTest { assertEquals("OPEN", i.getAccessright().getClassid()); }); assertEquals("0001", d.getInstance().get(0).getRefereed().getClassid()); + assertNotNull(d.getInstance().get(0).getPid()); + assertTrue(d.getInstance().get(0).getPid().size() == 1); + assertEquals("doi", d.getInstance().get(0).getPid().get(0).getQualifier().getClassid()); + assertEquals("10.5281/zenodo.3234526", d.getInstance().get(0).getPid().get(0).getValue()); assertValidId(r1.getSource()); assertValidId(r1.getTarget()); From 765f9bdee74da4fbc7291ed667ed05af427d52ca Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 9 Mar 2021 11:37:41 +0100 Subject: [PATCH 142/445] merged from dhp_oaf_model --- .../dhp/schema/oaf/CleaningFunctions.java | 4 ++-- .../dhp/schema/oaf/OafMapperUtils.java | 6 +++--- .../schema/oaf/utils/IdentifierFactory.java | 20 +++++++++++++++++++ .../oaf/utils/IdentifierFactoryTest.java | 2 +- .../schema/oaf/utils/publication_doi2.json | 2 +- 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index d21a58395..63db12d17 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -13,7 +13,7 @@ import eu.dnetlib.dhp.schema.common.ModelConstants; public class CleaningFunctions { - public static final String DOI_PREFIX_REGEX = "^.*10\\."; + public static final String DOI_PREFIX_REGEX = "(^10\\.|\\/10.)"; public static final String ORCID_PREFIX_REGEX = "^http(s?):\\/\\/orcid\\.org\\/"; public static final String CLEANING_REGEX = "(?:\\n|\\r|\\t)"; @@ -277,7 +277,7 @@ public class CleaningFunctions { // TODO add cleaning for more PID types as needed case "doi": - pid.setValue(value.toLowerCase().replaceAll(DOI_URL_PREFIX_REGEX, "")); + pid.setValue(value.toLowerCase().replaceAll(DOI_PREFIX_REGEX, "10.")); break; } return pid; diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java index 66f843056..d1dc1f8c3 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java @@ -9,9 +9,9 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; +import eu.dnetlib.dhp.schema.common.AccessRightComparator; import org.apache.commons.lang3.StringUtils; -import eu.dnetlib.dhp.schema.common.LicenseComparator; import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.utils.DHPUtils; @@ -327,10 +327,10 @@ public class OafMapperUtils { protected static Qualifier getBestAccessRights(final List instanceList) { if (instanceList != null) { - final Optional min = instanceList + final Optional min = instanceList .stream() .map(i -> i.getAccessright()) - .min(new LicenseComparator()); + .min(new AccessRightComparator<>()); final Qualifier rights = min.isPresent() ? min.get() : new Qualifier(); diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index fd90ee126..7df4f79d3 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -8,6 +8,11 @@ import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import eu.dnetlib.dhp.schema.common.ModelConstants; +import eu.dnetlib.dhp.schema.oaf.KeyValue; import org.apache.commons.lang3.StringUtils; import eu.dnetlib.dhp.schema.oaf.CleaningFunctions; @@ -30,6 +35,13 @@ public class IdentifierFactory implements Serializable { public static final int ID_PREFIX_LEN = 12; + public static final HashBiMap PID_AUTHORITY = HashBiMap.create(2); + + static { + PID_AUTHORITY.put(ModelConstants.CROSSREF_ID, "Crossref"); + PID_AUTHORITY.put(ModelConstants.DATACITE_ID, "Datacite"); + } + /** * Creates an identifier from the most relevant PID (if available) in the given entity T. Returns entity.id * when no PID is available @@ -43,6 +55,14 @@ public class IdentifierFactory implements Serializable { return entity.getId(); } + if (Optional.ofNullable( + entity.getCollectedfrom()) + .map(c -> c.stream() + .noneMatch(cf -> PID_AUTHORITY.containsKey(cf.getKey()) || PID_AUTHORITY.containsValue(cf.getValue()))) + .orElse(true)) { + return entity.getId(); + } + Map> pids = entity .getPid() .stream() diff --git a/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java index 17f172a42..14a5cf7df 100644 --- a/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java +++ b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java @@ -23,7 +23,7 @@ public class IdentifierFactoryTest { public void testCreateIdentifierForPublication() throws IOException { verifyIdentifier( - "publication_doi1.json", "50|doi_________::" + DHPUtils.md5("10.1016/j.cmet.2011.03.013"), true); + "publication_doi1.json", "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f", false); verifyIdentifier( "publication_doi2.json", "50|doi_________::" + DHPUtils.md5("10.1016/j.cmet.2010.03.013"), true); verifyIdentifier("publication_pmc1.json", "50|pmc_________::" + DHPUtils.md5("21459329"), true); diff --git a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi2.json b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi2.json index 7cc27f440..f2b53aa28 100644 --- a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi2.json +++ b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi2.json @@ -1 +1 @@ -{"id":"50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f","pid":[{"qualifier":{"classid":"doi"},"value":"10.1016/j.cmet.2011.03.013"},{"qualifier":{"classid":"doi"},"value":"10.1016/j.cmet.2010.03.013"},{"qualifier":{"classid":"urn"},"value":"urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2"},{"qualifier":{"classid":"scp-number"},"value":"79953761260"},{"qualifier":{"classid":"pmc"},"value":"21459329"}]} \ No newline at end of file +{"id":"50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f","pid":[{"qualifier":{"classid":"doi"},"value":"10.1016/j.cmet.2010.03.013"},{"qualifier":{"classid":"urn"},"value":"urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2"},{"qualifier":{"classid":"scp-number"},"value":"79953761260"},{"qualifier":{"classid":"pmc"},"value":"21459329"}],"collectedfrom":[{"key":"10|openaire____::081b82f96300b6a6e3d282bad31cb6e2","value":"Crossref"}]} From b3f3b895e5cf802e7f1c6f7099685bb8f0b66883 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 12 Jan 2021 15:39:18 +0100 Subject: [PATCH 143/445] [#6282 open access status in the Graph] OAStatus renamed as openAccessRoute --- .../eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java | 4 ++-- .../java/eu/dnetlib/dhp/schema/oaf/AccessRight.java | 12 ++++++------ .../oaf/{OAStatus.java => OpenAccessRoute.java} | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) rename dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/{OAStatus.java => OpenAccessRoute.java} (91%) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java index d1dc1f8c3..600494a43 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java @@ -118,13 +118,13 @@ public class OafMapperUtils { final String classname, final String schemeid, final String schemename, - final OAStatus oaStatus) { + final OpenAccessRoute openAccessRoute) { final AccessRight accessRight = new AccessRight(); accessRight.setClassid(classid); accessRight.setClassname(classname); accessRight.setSchemeid(schemeid); accessRight.setSchemename(schemename); - accessRight.setOaStatus(oaStatus); + accessRight.setOpenAccessRoute(openAccessRoute); return accessRight; } diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/AccessRight.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/AccessRight.java index ef67f7d4b..47de36504 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/AccessRight.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/AccessRight.java @@ -8,20 +8,20 @@ import java.util.Optional; */ public class AccessRight extends Qualifier { - private OAStatus oaStatus; + private OpenAccessRoute openAccessRoute; - public OAStatus getOaStatus() { - return oaStatus; + public OpenAccessRoute getOpenAccessRoute() { + return openAccessRoute; } - public void setOaStatus(OAStatus oaStatus) { - this.oaStatus = oaStatus; + public void setOpenAccessRoute(OpenAccessRoute openAccessRoute) { + this.openAccessRoute = openAccessRoute; } public String toComparableString() { String s = super.toComparableString(); return Optional - .ofNullable(getOaStatus()) + .ofNullable(getOpenAccessRoute()) .map(x -> s + "::" + x.toString()) .orElse(s); } diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/OAStatus.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/OpenAccessRoute.java similarity index 91% rename from dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/OAStatus.java rename to dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/OpenAccessRoute.java index 1d1cbf0aa..bec550411 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/OAStatus.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/OpenAccessRoute.java @@ -6,7 +6,7 @@ package eu.dnetlib.dhp.schema.oaf; * * https://support.unpaywall.org/support/solutions/articles/44001777288-what-do-the-types-of-oa-status-green-gold-hybrid-and-bronze-mean- */ -public enum OAStatus { +public enum OpenAccessRoute { gold, green, hybrid, bronze From 01630f638d9ebbbe680bb47118edadaf0b4d13dc Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 9 Mar 2021 17:11:50 +0100 Subject: [PATCH 144/445] IdentifierFactory implementation based on the list of datasources authoritative for a given pid type --- .../dhp/schema/oaf/CleaningFunctions.java | 27 +++- .../dhp/schema/oaf/OafMapperUtils.java | 2 +- .../schema/oaf/utils/IdentifierFactory.java | 116 ++++++++++++------ .../oaf/utils/BlackListProviderTest.java | 6 +- .../oaf/utils/IdentifierFactoryTest.java | 23 ++-- .../schema/oaf/utils/publication_doi1.json | 34 ++++- .../schema/oaf/utils/publication_doi2.json | 38 +++++- .../schema/oaf/utils/publication_doi3.json | 37 ++++++ .../schema/oaf/utils/publication_pmc2.json | 21 ++++ .../schema/oaf/utils/publication_urn1.json | 24 +++- .../dhp/schema/common/ModelConstants.java | 3 + .../raw/AbstractMdRecordToOafMapper.java | 12 +- .../dnetlib/dhp/oa/graph/raw/MappersTest.java | 4 +- 13 files changed, 289 insertions(+), 58 deletions(-) create mode 100644 dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi3.json create mode 100644 dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_pmc2.json diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index 63db12d17..21bec2d2d 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -14,6 +14,8 @@ import eu.dnetlib.dhp.schema.common.ModelConstants; public class CleaningFunctions { public static final String DOI_PREFIX_REGEX = "(^10\\.|\\/10.)"; + public static final String DOI_PREFIX = "10."; + public static final String ORCID_PREFIX_REGEX = "^http(s?):\\/\\/orcid\\.org\\/"; public static final String CLEANING_REGEX = "(?:\\n|\\r|\\t)"; @@ -263,6 +265,29 @@ public class CleaningFunctions { classid, classname, scheme, scheme); } + /** + * Utility method that filter PID values on a per-type basis. + * @param pid the PID whose value will be checked. + * @return true the PID containing the normalised value. + */ + private static boolean filterPid(StructuredProperty pid) { + String value = Optional + .ofNullable(pid.getValue()) + .map(s -> StringUtils.replaceAll(s, "\\s", "")) + .orElse(""); + if (StringUtils.isBlank(value)) { + return false; + } + switch (pid.getQualifier().getClassid()) { + + // TODO add cleaning for more PID types as needed + case "doi": + return value.startsWith(DOI_PREFIX); + default: + return true; + } + } + /** * Utility method that normalises PID values on a per-type basis. * @param pid the PID whose value will be normalised. @@ -277,7 +302,7 @@ public class CleaningFunctions { // TODO add cleaning for more PID types as needed case "doi": - pid.setValue(value.toLowerCase().replaceAll(DOI_PREFIX_REGEX, "10.")); + pid.setValue(value.toLowerCase().replaceFirst(DOI_PREFIX_REGEX, DOI_PREFIX)); break; } return pid; diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java index 600494a43..e79351bc6 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java @@ -9,9 +9,9 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; -import eu.dnetlib.dhp.schema.common.AccessRightComparator; import org.apache.commons.lang3.StringUtils; +import eu.dnetlib.dhp.schema.common.AccessRightComparator; import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.utils.DHPUtils; diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 7df4f79d3..fc553ced1 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -1,23 +1,23 @@ package eu.dnetlib.dhp.schema.oaf.utils; +import static eu.dnetlib.dhp.schema.common.ModelConstants.*; + import java.io.Serializable; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; +import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.apache.commons.lang3.StringUtils; import com.google.common.collect.HashBiMap; import com.google.common.collect.Maps; import com.google.common.collect.Sets; -import eu.dnetlib.dhp.schema.common.ModelConstants; -import eu.dnetlib.dhp.schema.oaf.KeyValue; -import org.apache.commons.lang3.StringUtils; -import eu.dnetlib.dhp.schema.oaf.CleaningFunctions; -import eu.dnetlib.dhp.schema.oaf.OafEntity; -import eu.dnetlib.dhp.schema.oaf.StructuredProperty; +import eu.dnetlib.dhp.schema.common.ModelConstants; +import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.dhp.utils.DHPUtils; /** @@ -35,44 +35,37 @@ public class IdentifierFactory implements Serializable { public static final int ID_PREFIX_LEN = 12; - public static final HashBiMap PID_AUTHORITY = HashBiMap.create(2); + /** + * Declares the associations PID_TYPE -> [DATASOURCE ID, NAME] considered authoritative for that PID + */ + public static final Map> PID_AUTHORITY = Maps.newHashMap(); static { - PID_AUTHORITY.put(ModelConstants.CROSSREF_ID, "Crossref"); - PID_AUTHORITY.put(ModelConstants.DATACITE_ID, "Datacite"); + PID_AUTHORITY.put(PidType.doi, HashBiMap.create()); + PID_AUTHORITY.get(PidType.doi).put(CROSSREF_ID, "Crossref"); + PID_AUTHORITY.get(PidType.doi).put(DATACITE_ID, "Datacite"); + + PID_AUTHORITY.put(PidType.pmc, HashBiMap.create()); + PID_AUTHORITY.get(PidType.pmc).put(EUROPE_PUBMED_CENTRAL_ID, "Europe PubMed Central"); + PID_AUTHORITY.get(PidType.pmc).put(PUBMED_CENTRAL_ID, "PubMed Central"); + + PID_AUTHORITY.put(PidType.pmid, HashBiMap.create()); + PID_AUTHORITY.get(PidType.pmid).put(EUROPE_PUBMED_CENTRAL_ID, "Europe PubMed Central"); + PID_AUTHORITY.get(PidType.pmid).put(PUBMED_CENTRAL_ID, "PubMed Central"); } /** - * Creates an identifier from the most relevant PID (if available) in the given entity T. Returns entity.id - * when no PID is available + * Creates an identifier from the most relevant PID (if available) provided by a known PID authority in the given + * entity T. Returns entity.id when none of the PIDs meet the selection criteria is available. + * * @param entity the entity providing PIDs and a default ID. * @param the specific entity type. Currently Organization and Result subclasses are supported. * @param md5 indicates whether should hash the PID value or not. * @return an identifier from the most relevant PID, entity.id otherwise */ public static String createIdentifier(T entity, boolean md5) { - if (Objects.isNull(entity.getPid()) || entity.getPid().isEmpty()) { - return entity.getId(); - } - if (Optional.ofNullable( - entity.getCollectedfrom()) - .map(c -> c.stream() - .noneMatch(cf -> PID_AUTHORITY.containsKey(cf.getKey()) || PID_AUTHORITY.containsValue(cf.getValue()))) - .orElse(true)) { - return entity.getId(); - } - - Map> pids = entity - .getPid() - .stream() - .map(CleaningFunctions::normalizePidValue) - .filter(IdentifierFactory::pidFilter) - .collect( - Collectors - .groupingBy( - p -> p.getQualifier().getClassid(), - Collectors.mapping(p -> p, Collectors.toList()))); + final Map> pids = extractPids(entity); return pids .values() @@ -93,6 +86,57 @@ public class IdentifierFactory implements Serializable { .orElseGet(entity::getId); } + private static Map> extractPids(T entity) { + if (entity instanceof Result) { + return Optional + .ofNullable(((Result) entity).getInstance()) + .map( + instance -> instance + .stream() + .map( + i -> Optional + .ofNullable(i.getPid()) + .map( + pp -> pp + .stream() + // filter away PIDs provided by a DS that is not considered an authority for the + // given PID Type + .filter(p -> { + final PidType pType = PidType.tryValueOf(p.getQualifier().getClassid()); + return Optional.ofNullable(i.getCollectedfrom()).isPresent() && + Optional + .ofNullable(PID_AUTHORITY.get(pType)) + .map(authorities -> { + final KeyValue cf = i.getCollectedfrom(); + return authorities.containsKey(cf.getKey()) + || authorities.containsValue(cf.getValue()); + }) + .orElse(false); + }) + .map(CleaningFunctions::normalizePidValue) + .filter(IdentifierFactory::pidFilter)) + .orElse(Stream.empty())) + .flatMap(Function.identity()) + .collect( + Collectors + .groupingBy( + p -> p.getQualifier().getClassid(), + Collectors.mapping(p -> p, Collectors.toList())))) + .orElse(new HashMap<>()); + } else { + return entity + .getPid() + .stream() + .map(CleaningFunctions::normalizePidValue) + .filter(IdentifierFactory::pidFilter) + .collect( + Collectors + .groupingBy( + p -> p.getQualifier().getClassid(), + Collectors.mapping(p -> p, Collectors.toList()))); + } + } + /** * @see {@link IdentifierFactory#createIdentifier(OafEntity, boolean)} */ diff --git a/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/BlackListProviderTest.java b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/BlackListProviderTest.java index 7cab5ed9c..203cda0ca 100644 --- a/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/BlackListProviderTest.java +++ b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/BlackListProviderTest.java @@ -1,6 +1,8 @@ package eu.dnetlib.dhp.schema.oaf.utils; +import java.util.Set; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -12,6 +14,8 @@ public class BlackListProviderTest { Assertions.assertNotNull(PidBlacklistProvider.getBlacklist()); Assertions.assertNotNull(PidBlacklistProvider.getBlacklist().get("doi")); Assertions.assertTrue(PidBlacklistProvider.getBlacklist().get("doi").size() > 0); - Assertions.assertNull(PidBlacklistProvider.getBlacklist("xxx")); + final Set xxx = PidBlacklistProvider.getBlacklist("xxx"); + Assertions.assertNotNull(xxx); + Assertions.assertEquals(0, xxx.size()); } } diff --git a/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java index 14a5cf7df..31ef91a7a 100644 --- a/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java +++ b/dhp-common/src/test/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactoryTest.java @@ -23,28 +23,35 @@ public class IdentifierFactoryTest { public void testCreateIdentifierForPublication() throws IOException { verifyIdentifier( - "publication_doi1.json", "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f", false); + "publication_doi1.json", "50|doi_________::79dbc7a2a56dc1532659f9038843256e", true); + verifyIdentifier( - "publication_doi2.json", "50|doi_________::" + DHPUtils.md5("10.1016/j.cmet.2010.03.013"), true); - verifyIdentifier("publication_pmc1.json", "50|pmc_________::" + DHPUtils.md5("21459329"), true); + "publication_doi2.json", "50|doi_________::79dbc7a2a56dc1532659f9038843256e", true); + verifyIdentifier( - "publication_urn1.json", - "50|urn_________::" + DHPUtils.md5("urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2"), true); + "publication_doi3.json", "50|pmc_________::94e4cb08c93f8733b48e2445d04002ac", true); + + verifyIdentifier( + "publication_pmc1.json", "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f", true); + + verifyIdentifier( + "publication_pmc2.json", "50|pmc_________::94e4cb08c93f8733b48e2445d04002ac", true); final String defaultID = "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f"; verifyIdentifier("publication_3.json", defaultID, true); verifyIdentifier("publication_4.json", defaultID, true); verifyIdentifier("publication_5.json", defaultID, true); + } @Test public void testCreateIdentifierForPublicationNoHash() throws IOException { - verifyIdentifier("publication_doi1.json", "50|doi_________::10.1016/j.cmet.2011.03.013", false); + verifyIdentifier("publication_doi1.json", "50|doi_________::10.1016/j.cmet.2010.03.013", false); verifyIdentifier("publication_doi2.json", "50|doi_________::10.1016/j.cmet.2010.03.013", false); - verifyIdentifier("publication_pmc1.json", "50|pmc_________::21459329", false); + verifyIdentifier("publication_pmc1.json", "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f", false); verifyIdentifier( - "publication_urn1.json", "50|urn_________::urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2", false); + "publication_urn1.json", "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f", false); final String defaultID = "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f"; verifyIdentifier("publication_3.json", defaultID, false); diff --git a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi1.json b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi1.json index da90a64d0..83bc0cd20 100644 --- a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi1.json +++ b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi1.json @@ -1 +1,33 @@ -{"id":"50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f","pid":[ {"qualifier":{"classid":"doi"},"value":"10.12739/10.12739"},{"qualifier":{"classid":"doi"},"value":"10.1016/j.cmet.2011.03.013"},{"qualifier":{"classid":"urn"},"value":"urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2"},{"qualifier":{"classid":"scp-number"},"value":"79953761260"},{"qualifier":{"classid":"pmc"},"value":"21459329"}]} \ No newline at end of file +{ + "id": "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f", + "instance": [ + { + "collectedfrom": { + "key": "10|openaire____::081b82f96300b6a6e3d282bad31cb6e2", + "value": "Crossref" + }, + "pid": [ + { + "qualifier": {"classid": "doi"}, + "value": "10.1016/j.cmet.2010.03.013" + } + ] + }, + { + "pid": [ + { + "qualifier": {"classid": "urn"}, + "value": "urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2" + }, + { + "qualifier": {"classid": "scp-number"}, + "value": "79953761260" + }, + { + "qualifier": {"classid": "pmc"}, + "value": "21459329" + } + ] + } + ] +} \ No newline at end of file diff --git a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi2.json b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi2.json index f2b53aa28..5c73fc3c7 100644 --- a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi2.json +++ b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi2.json @@ -1 +1,37 @@ -{"id":"50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f","pid":[{"qualifier":{"classid":"doi"},"value":"10.1016/j.cmet.2010.03.013"},{"qualifier":{"classid":"urn"},"value":"urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2"},{"qualifier":{"classid":"scp-number"},"value":"79953761260"},{"qualifier":{"classid":"pmc"},"value":"21459329"}],"collectedfrom":[{"key":"10|openaire____::081b82f96300b6a6e3d282bad31cb6e2","value":"Crossref"}]} +{ + "id": "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f", + "instance": [ + { + "collectedfrom": { + "key": "10|openaire____::081b82f96300b6a6e3d282bad31cb6e2", + "value": "Crossref" + }, + "pid": [ + { + "qualifier": {"classid": "doi"}, + "value": "10.1016/j.cmet.2010.03.013" + } + ] + }, + { + "collectedfrom": { + "key": "10|opendoar____::8b6dd7db9af49e67306feb59a8bdc52c", + "value": "Europe PubMed Central" + }, + "pid": [ + { + "qualifier": {"classid": "urn"}, + "value": "urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2" + }, + { + "qualifier": {"classid": "scp-number"}, + "value": "79953761260" + }, + { + "qualifier": {"classid": "pmc"}, + "value": "21459329" + } + ] + } + ] +} \ No newline at end of file diff --git a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi3.json b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi3.json new file mode 100644 index 000000000..97c40d4bb --- /dev/null +++ b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_doi3.json @@ -0,0 +1,37 @@ +{ + "id": "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f", + "instance": [ + { + "collectedfrom": { + "key": "10|openaire____::1234", + "value": "Zenodo" + }, + "pid": [ + { + "qualifier": {"classid": "doi"}, + "value": "10.1016/j.cmet.2010.03.013" + } + ] + }, + { + "collectedfrom": { + "key": "10|opendoar____::8b6dd7db9af49e67306feb59a8bdc52c", + "value": "Europe PubMed Central" + }, + "pid": [ + { + "qualifier": {"classid": "urn"}, + "value": "urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2" + }, + { + "qualifier": {"classid": "scp-number"}, + "value": "79953761260" + }, + { + "qualifier": {"classid": "pmc"}, + "value": "21459329" + } + ] + } + ] +} \ No newline at end of file diff --git a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_pmc2.json b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_pmc2.json new file mode 100644 index 000000000..e7d49eebb --- /dev/null +++ b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_pmc2.json @@ -0,0 +1,21 @@ +{ + "id":"50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f", + "instance": [ + { + "collectedfrom": { + "key": "10|opendoar____::8b6dd7db9af49e67306feb59a8bdc52c", + "value": "Europe PubMed Central" + }, + "pid": [ + { + "qualifier": {"classid": "doi"}, + "value": "10.1016/j.cmet.2010.03.013" + }, + { + "qualifier":{"classid":"pmc"}, + "value":"21459329" + } + ] + } + ] +} \ No newline at end of file diff --git a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_urn1.json b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_urn1.json index 83e14e48d..5323ac8bd 100644 --- a/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_urn1.json +++ b/dhp-common/src/test/resources/eu/dnetlib/dhp/schema/oaf/utils/publication_urn1.json @@ -1 +1,23 @@ -{"id":"50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f","pid":[{"qualifier":{"classid":"urn"},"value":"urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2"},{"qualifier":{"classid":"scp-number"},"value":"79953761260"},{"qualifier":{"classid":"pmcid"},"value":"21459329"}]} \ No newline at end of file +{ + "id": "50|DansKnawCris::0829b5191605bdbea36d6502b8c1ce1f", + "pid": [ + { + "qualifier": { + "classid": "urn" + }, + "value": "urn:nbn:nl:ui:29-f3ed5f9e-edf6-457e-8848-61b58a4075e2" + }, + { + "qualifier": { + "classid": "scp-number" + }, + "value": "79953761260" + }, + { + "qualifier": { + "classid": "pmcid" + }, + "value": "21459329" + } + ] +} \ No newline at end of file diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java index 143340b07..f2e6a5a30 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java @@ -14,6 +14,9 @@ public class ModelConstants { public static String CROSSREF_ID = "10|openaire____::081b82f96300b6a6e3d282bad31cb6e2"; public static String DATACITE_ID = "10|openaire____::9e3be59865b2c1c335d32dae2fe7b254"; + public static String EUROPE_PUBMED_CENTRAL_ID = "10|opendoar____::8b6dd7db9af49e67306feb59a8bdc52c"; + public static String PUBMED_CENTRAL_ID = "10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357"; + public static final String DNET_SUBJECT_TYPOLOGIES = "dnet:subject_classification_typologies"; public static final String DNET_RESULT_TYPOLOGIES = "dnet:result_typologies"; public static final String DNET_PUBLICATION_RESOURCE = "dnet:publication_resource"; diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java index 561c6a6b8..d56971220 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java @@ -236,11 +236,11 @@ public abstract class AbstractMdRecordToOafMapper { } protected Relation getRelation(final String source, - final String target, - final String relType, - final String subRelType, - final String relClass, - final OafEntity entity) { + final String target, + final String relType, + final String subRelType, + final String relClass, + final OafEntity entity) { return getRelation(source, target, relType, subRelType, relClass, entity, null); } @@ -250,7 +250,7 @@ public abstract class AbstractMdRecordToOafMapper { final String subRelType, final String relClass, final OafEntity entity, - final String validationDate) { + final String validationDate) { final Relation rel = new Relation(); rel.setRelType(relType); rel.setSubRelType(subRelType); diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java index 37d5bf33b..fb79610c8 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java @@ -71,7 +71,7 @@ public class MappersTest { assertValidId(p.getId()); - assertEquals(2, p.getOriginalId().size()); + assertEquals(1, p.getOriginalId().size()); assertTrue(p.getOriginalId().contains("10.3897/oneeco.2.e13718")); assertValidId(p.getCollectedfrom().get(0).getKey()); @@ -186,7 +186,7 @@ public class MappersTest { final Relation r2 = (Relation) list.get(2); assertValidId(d.getId()); - assertEquals(2, d.getOriginalId().size()); + assertEquals(1, d.getOriginalId().size()); assertTrue(d.getOriginalId().contains("oai:zenodo.org:3234526")); assertValidId(d.getCollectedfrom().get(0).getKey()); assertTrue(StringUtils.isNotBlank(d.getTitle().get(0).getValue())); From 9917d7e01c6ba3e2299b67275a770c9a97edd397 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 9 Mar 2021 17:12:52 +0100 Subject: [PATCH 145/445] PID authorities include ArXiv --- .../dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java | 3 +++ .../eu/dnetlib/dhp/schema/common/ModelConstants.java | 9 +++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index fc553ced1..2c4cd29e8 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -52,6 +52,9 @@ public class IdentifierFactory implements Serializable { PID_AUTHORITY.put(PidType.pmid, HashBiMap.create()); PID_AUTHORITY.get(PidType.pmid).put(EUROPE_PUBMED_CENTRAL_ID, "Europe PubMed Central"); PID_AUTHORITY.get(PidType.pmid).put(PUBMED_CENTRAL_ID, "PubMed Central"); + + PID_AUTHORITY.put(PidType.arXiv, HashBiMap.create()); + PID_AUTHORITY.get(PidType.arXiv).put(ARXIV_ID, "arXiv.org e-Print Archive"); } /** diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java index f2e6a5a30..68e37b9b7 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java @@ -11,11 +11,12 @@ public class ModelConstants { public static final String ORCID_PENDING = "orcid_pending"; public static final String ORCID_CLASSNAME = "Open Researcher and Contributor ID"; - public static String CROSSREF_ID = "10|openaire____::081b82f96300b6a6e3d282bad31cb6e2"; - public static String DATACITE_ID = "10|openaire____::9e3be59865b2c1c335d32dae2fe7b254"; + public static final String CROSSREF_ID = "10|openaire____::081b82f96300b6a6e3d282bad31cb6e2"; + public static final String DATACITE_ID = "10|openaire____::9e3be59865b2c1c335d32dae2fe7b254"; - public static String EUROPE_PUBMED_CENTRAL_ID = "10|opendoar____::8b6dd7db9af49e67306feb59a8bdc52c"; - public static String PUBMED_CENTRAL_ID = "10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357"; + public static final String EUROPE_PUBMED_CENTRAL_ID = "10|opendoar____::8b6dd7db9af49e67306feb59a8bdc52c"; + public static final String PUBMED_CENTRAL_ID = "10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357"; + public static final String ARXIV_ID = "10|opendoar____::6f4922f45568161a8cdf4ad2299f6d23"; public static final String DNET_SUBJECT_TYPOLOGIES = "dnet:subject_classification_typologies"; public static final String DNET_RESULT_TYPOLOGIES = "dnet:result_typologies"; From c801ab6c1d08f90fa16aa6c6a6174d4ba6cbff74 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 9 Mar 2021 17:22:31 +0100 Subject: [PATCH 146/445] minor --- .../eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 2c4cd29e8..40279d29d 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -8,15 +8,12 @@ import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import java.util.stream.StreamSupport; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.HashBiMap; import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.dhp.utils.DHPUtils; @@ -36,7 +33,7 @@ public class IdentifierFactory implements Serializable { public static final int ID_PREFIX_LEN = 12; /** - * Declares the associations PID_TYPE -> [DATASOURCE ID, NAME] considered authoritative for that PID + * Declares the associations PID_TYPE -> [DATASOURCE ID, NAME] considered authoritative for that PID_TYPE */ public static final Map> PID_AUTHORITY = Maps.newHashMap(); From f74e46494276f7b0e14ff549ad366ee53eb69d02 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 10 Mar 2021 15:40:05 +0100 Subject: [PATCH 147/445] create bestaccessright as Qualifier --- .../eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java index e79351bc6..b0e8a65d2 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/OafMapperUtils.java @@ -141,6 +141,15 @@ public class OafMapperUtils { return q; } + public static Qualifier qualifier(final Qualifier qualifier) { + final Qualifier q = new Qualifier(); + q.setClassid(qualifier.getClassid()); + q.setClassname(qualifier.getClassname()); + q.setSchemeid(qualifier.getSchemeid()); + q.setSchemename(qualifier.getSchemename()); + return q; + } + public static StructuredProperty structuredProperty( final String value, final String classid, @@ -332,7 +341,7 @@ public class OafMapperUtils { .map(i -> i.getAccessright()) .min(new AccessRightComparator<>()); - final Qualifier rights = min.isPresent() ? min.get() : new Qualifier(); + final Qualifier rights = min.isPresent() ? qualifier(min.get()) : new Qualifier(); if (StringUtils.isBlank(rights.getClassid())) { rights.setClassid(UNKNOWN); From f5e7c57654d641c9c5b6c909483cb4b2a2383ca8 Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Thu, 11 Mar 2021 10:32:45 +0100 Subject: [PATCH 148/445] Fixed ticket 6282 --- .../dhp/schema/common/ModelConstants.java | 3 ++ .../dnetlib/doiboost/uw/UnpayWallToOAF.scala | 46 +++++++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java index 68e37b9b7..1a0117fb9 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java @@ -18,6 +18,9 @@ public class ModelConstants { public static final String PUBMED_CENTRAL_ID = "10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357"; public static final String ARXIV_ID = "10|opendoar____::6f4922f45568161a8cdf4ad2299f6d23"; + //VOCABULARY VALUE + public static final String ACCESS_RIGHT_OPEN = "OPEN"; + public static final String DNET_SUBJECT_TYPOLOGIES = "dnet:subject_classification_typologies"; public static final String DNET_RESULT_TYPOLOGIES = "dnet:result_typologies"; public static final String DNET_PUBLICATION_RESOURCE = "dnet:publication_resource"; diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala index a26f57f0f..7119bbc12 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala @@ -1,7 +1,8 @@ package eu.dnetlib.doiboost.uw +import eu.dnetlib.dhp.schema.common.ModelConstants import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory -import eu.dnetlib.dhp.schema.oaf.{Instance, Publication} +import eu.dnetlib.dhp.schema.oaf.{AccessRight, Instance, OpenAccessRoute, Publication} import org.json4s import org.json4s.DefaultFormats import org.json4s.jackson.JsonMethods.parse @@ -21,6 +22,31 @@ case class OALocation(evidence:Option[String], host_type:Option[String], is_best object UnpayWallToOAF { val logger: Logger = LoggerFactory.getLogger(getClass) + def get_color(is_oa:Boolean, location: OALocation, journal_is_oa:Boolean):Option[OpenAccessRoute] = { + if (is_oa) { + if (location.host_type.isDefined) { + { + if (location.host_type.get.equalsIgnoreCase("repository")) + return Some(OpenAccessRoute.green) + else if (location.host_type.get.equalsIgnoreCase("publisher")) { + if (journal_is_oa) + return Some(OpenAccessRoute.gold) + else { + if (location.license.isDefined) + return Some(OpenAccessRoute.hybrid) + else + return Some(OpenAccessRoute.bronze) + } + + } + } + + } + } + None + } + + def convertToOAF(input:String):Publication = { val pub = new Publication @@ -32,13 +58,17 @@ object UnpayWallToOAF { val is_oa = (json\ "is_oa").extract[Boolean] + val journal_is_oa= (json\ "journal_is_oa").extract[Boolean] + val oaLocation:OALocation = (json \ "best_oa_location").extractOrElse[OALocation](null) + + val colour = get_color(is_oa, oaLocation, journal_is_oa) pub.setPid(List(createSP(doi, "doi", PID_TYPES)).asJava) //IMPORTANT //The old method pub.setId(IdentifierFactory.createIdentifier(pub)) //will be replaced using IdentifierFactory - pub.setId(generateIdentifier(pub, doi.toLowerCase)) + //pub.setId(generateIdentifier(pub, doi.toLowerCase)) pub.setId(IdentifierFactory.createIdentifier(pub)) @@ -61,8 +91,18 @@ object UnpayWallToOAF { if (oaLocation.license.isDefined) i.setLicense(asField(oaLocation.license.get)) - pub.setInstance(List(i).asJava) + + // Ticket #6282 Adding open Access Colour + if (colour.isDefined) { + val a = new AccessRight + a.setClassid(ModelConstants.ACCESS_RIGHT_OPEN) + a.setClassname(ModelConstants.ACCESS_RIGHT_OPEN) + a.setSchemeid(ModelConstants.DNET_ACCESS_MODES) + a.setSchemename(ModelConstants.DNET_ACCESS_MODES) + a.setOpenAccessRoute(colour.get) + } + pub.setInstance(List(i).asJava) pub } From a8e5d0ea0dc5d450b4a1139c5e99592df22036de Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Thu, 11 Mar 2021 10:41:24 +0100 Subject: [PATCH 149/445] updated test and fixed assign of access right --- .../eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala | 3 ++- .../doiboost/uw/UnpayWallMappingTest.scala | 16 +++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala index 7119bbc12..0972d55de 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala @@ -83,7 +83,7 @@ object UnpayWallToOAF { val i :Instance= new Instance() i.setCollectedfrom(createUnpayWallCollectedFrom()) - i.setAccessright(getOpenAccessQualifier()) +// i.setAccessright(getOpenAccessQualifier()) i.setUrl(List(oaLocation.url.get).asJava) // Ticket #6281 added pid to Instance @@ -101,6 +101,7 @@ object UnpayWallToOAF { a.setSchemeid(ModelConstants.DNET_ACCESS_MODES) a.setSchemename(ModelConstants.DNET_ACCESS_MODES) a.setOpenAccessRoute(colour.get) + i.setAccessright(a) } pub.setInstance(List(i).asJava) pub diff --git a/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/uw/UnpayWallMappingTest.scala b/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/uw/UnpayWallMappingTest.scala index 8a6dd279b..3e4acdffb 100644 --- a/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/uw/UnpayWallMappingTest.scala +++ b/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/uw/UnpayWallMappingTest.scala @@ -1,6 +1,8 @@ package eu.dnetlib.doiboost.uw -import org.codehaus.jackson.map.{ObjectMapper, SerializationConfig} + +import com.fasterxml.jackson.databind.ObjectMapper +import eu.dnetlib.dhp.schema.oaf.OpenAccessRoute import org.junit.jupiter.api.Test import scala.io.Source @@ -31,19 +33,15 @@ class UnpayWallMappingTest { assertNotNull(line) assertTrue(line.nonEmpty) } - mapper.getSerializationConfig.enable(SerializationConfig.Feature.INDENT_OUTPUT) + val l = Ilist.lines.next() - logger.info(mapper.writeValueAsString(UnpayWallToOAF.convertToOAF(l))) - - - - - - + val item = UnpayWallToOAF.convertToOAF(l) + assertEquals(item.getInstance().get(0).getAccessright.getOpenAccessRoute, OpenAccessRoute.bronze) + logger.info(mapper.writeValueAsString(item)) } } From 4bb3bcafa544cd5a4806c610d6346ff55e4356da Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Thu, 11 Mar 2021 11:32:32 +0100 Subject: [PATCH 150/445] add author sequence number --- .../java/eu/dnetlib/dhp/schema/oaf/Author.java | 1 + .../dnetlib/doiboost/crossref/Crossref2Oaf.scala | 12 +++++++++--- .../eu/dnetlib/doiboost/mag/MagDataModel.scala | 10 +++++----- .../dnetlib/doiboost/mag/SparkProcessMAG.scala | 4 ++-- .../eu/dnetlib/doiboost/crossref/article.json | 16 ++++++++-------- 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Author.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Author.java index 231fb1e60..b2f757d71 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Author.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Author.java @@ -12,6 +12,7 @@ public class Author implements Serializable { private String surname; + // START WITH 1 private Integer rank; private List pid; diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala index 9251bba0e..79194b283 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala @@ -21,7 +21,7 @@ case class CrossrefDT(doi: String, json:String, timestamp: Long) {} case class mappingAffiliation(name: String) {} -case class mappingAuthor(given: Option[String], family: String, ORCID: Option[String], affiliation: Option[mappingAffiliation]) {} +case class mappingAuthor(given: Option[String], family: String, sequence:Option[String], ORCID: Option[String], affiliation: Option[mappingAffiliation]) {} case class mappingFunder(name: String, DOI: Option[String], award: Option[List[String]]) {} @@ -162,7 +162,12 @@ case object Crossref2Oaf { //Mapping Author val authorList: List[mappingAuthor] = (json \ "author").extractOrElse[List[mappingAuthor]](List()) - result.setAuthor(authorList.map(a => generateAuhtor(a.given.orNull, a.family, a.ORCID.orNull)).asJava) + + + + val sorted_list = authorList.sortWith((a:mappingAuthor, b:mappingAuthor) => a.sequence.isDefined && a.sequence.get.equalsIgnoreCase("first")) + + result.setAuthor(sorted_list.zipWithIndex.map{case (a, index) => generateAuhtor(a.given.orNull, a.family, a.ORCID.orNull, index)}.asJava) // Mapping instance val instance = new Instance() @@ -205,11 +210,12 @@ case object Crossref2Oaf { } - def generateAuhtor(given: String, family: String, orcid: String): Author = { + def generateAuhtor(given: String, family: String, orcid: String, index:Int): Author = { val a = new Author a.setName(given) a.setSurname(family) a.setFullname(s"$given $family") + a.setRank(index+1) if (StringUtils.isNotBlank(orcid)) a.setPid(List(createSP(orcid, ORCID_PENDING, PID_TYPES, generateDataInfo())).asJava) diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala index 910fad0e2..987a81fba 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala @@ -32,11 +32,11 @@ case class MagAffiliation(AffiliationId: Long, Rank: Int, NormalizedName: String case class MagPaperAuthorAffiliation(PaperId: Long, AuthorId: Long, AffiliationId: Option[Long], AuthorSequenceNumber: Int, OriginalAuthor: String, OriginalAffiliation: String) {} -case class MagAuthorAffiliation(author: MagAuthor, affiliation:String) +case class MagAuthorAffiliation(author: MagAuthor, affiliation:String, sequenceNumber:Int) case class MagPaperWithAuthorList(PaperId: Long, authors: List[MagAuthorAffiliation]) {} -case class MagPaperAuthorDenormalized(PaperId: Long, author: MagAuthor, affiliation:String) {} +case class MagPaperAuthorDenormalized(PaperId: Long, author: MagAuthor, affiliation:String, sequenceNumber:Int) {} case class MagPaperUrl(PaperId: Long, SourceType: Option[Int], SourceUrl: Option[String], LanguageCode: Option[String]) {} @@ -209,9 +209,9 @@ case object ConversionUtil { val authorsOAF = authors.authors.map { f: MagAuthorAffiliation => val a: eu.dnetlib.dhp.schema.oaf.Author = new eu.dnetlib.dhp.schema.oaf.Author - - a.setFullname(f.author.DisplayName.get) - + a.setRank(f.sequenceNumber) + if (f.author.DisplayName.isDefined) + a.setFullname(f.author.DisplayName.get) if(f.affiliation!= null) a.setAffiliation(List(asField(f.affiliation)).asJava) a.setPid(List(createSP(s"https://academic.microsoft.com/#/detail/${f.author.AuthorId}", "URL", PID_TYPES)).asJava) diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/SparkProcessMAG.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/SparkProcessMAG.scala index 780e65c1e..bc1982e77 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/SparkProcessMAG.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/SparkProcessMAG.scala @@ -58,13 +58,13 @@ object SparkProcessMAG { val paperAuthorAffiliation = spark.read.load(s"$sourcePath/PaperAuthorAffiliations").as[MagPaperAuthorAffiliation] paperAuthorAffiliation.joinWith(authors, paperAuthorAffiliation("AuthorId").equalTo(authors("AuthorId"))) - .map { case (a: MagPaperAuthorAffiliation, b: MagAuthor) => (a.AffiliationId, MagPaperAuthorDenormalized(a.PaperId, b, null)) } + .map { case (a: MagPaperAuthorAffiliation, b: MagAuthor) => (a.AffiliationId, MagPaperAuthorDenormalized(a.PaperId, b, null, a.AuthorSequenceNumber)) } .joinWith(affiliation, affiliation("AffiliationId").equalTo(col("_1")), "left") .map(s => { val mpa = s._1._2 val af = s._2 if (af != null) { - MagPaperAuthorDenormalized(mpa.PaperId, mpa.author, af.DisplayName) + MagPaperAuthorDenormalized(mpa.PaperId, mpa.author, af.DisplayName, mpa.sequenceNumber) } else mpa }).groupBy("PaperId").agg(collect_list(struct($"author", $"affiliation")).as("authors")) diff --git a/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/crossref/article.json b/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/crossref/article.json index e0dc0db39..69424d0ad 100644 --- a/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/crossref/article.json +++ b/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/crossref/article.json @@ -12,14 +12,6 @@ "abstract": "A qualitative spot-test and tandem quantitative analysis of dipyrone in the bulk drugand in pharmaceutical preparations is proposed. The formation of a reddish-violet\u00a0 color indicates a positive result. In sequence a quantitative procedure can be performed in the same flask. The quantitative results obtained were statistically compared with those obtained with the method indicated by the Brazilian\u00a0 Pharmacopoeia, using the Student\u2019s t and the F tests. Considering the concentration in a 100 \u03bcL aliquot, the qualitative visual limit of detection is about 5\u00d710-6 g; instrumental LOD \u2245 1.4\u00d710-4 mol L-1 ; LOQ \u2245 4.5\u00d710-4 mol L-1.", "prefix": "10.26850", "author": [ - { - "authenticated-orcid": false, - "given": "Matthieu", - "family": "Tubino", - "sequence": "first", - "affiliation": [], - "ORCID": "http://orcid.org/0000-0002-1987-3907" - }, { "affiliation": [], "given": "A. C.", @@ -49,6 +41,14 @@ "sequence": "additional", "affiliation": [], "ORCID": "http://orcid.org/0000-0001-5564-1639" + }, + { + "authenticated-orcid": false, + "given": "Matthieu", + "family": "Tubino", + "sequence": "first", + "affiliation": [], + "ORCID": "http://orcid.org/0000-0002-1987-3907" } ], "reference-count": 0, From d3cb923f24462e845eff47aa5782e6fd74684d8f Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 11 Mar 2021 12:37:33 +0100 Subject: [PATCH 151/445] introduced java8-based date parsing --- .../java/eu/dnetlib/dhp/schema/common/ModelSupport.java | 9 +++++---- .../test/java/eu/dnetlib/dhp/schema/oaf/MergeTest.java | 5 +++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java index 0c7903137..a92e11b5a 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java @@ -5,6 +5,9 @@ import static com.google.common.base.Preconditions.checkArgument; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; import java.util.Date; import java.util.Map; import java.util.Objects; @@ -477,8 +480,6 @@ public class ModelSupport { return ((OafEntity) t).getId(); } - public static final String ISO8601FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"; - public static String oldest(String dateA, String dateB) throws ParseException { if (StringUtils.isBlank(dateA)) { @@ -489,8 +490,8 @@ public class ModelSupport { } if (StringUtils.isNotBlank(dateA) && StringUtils.isNotBlank(dateB)) { - final Date a = new SimpleDateFormat(ISO8601FORMAT).parse(dateA); - final Date b = new SimpleDateFormat(ISO8601FORMAT).parse(dateB); + final Date a = Date.from(Instant.from(DateTimeFormatter.ISO_INSTANT.parse(dateA))); + final Date b = Date.from(Instant.from(DateTimeFormatter.ISO_INSTANT.parse(dateB))); return a.before(b) ? dateA : dateB; } else { diff --git a/dhp-schemas/src/test/java/eu/dnetlib/dhp/schema/oaf/MergeTest.java b/dhp-schemas/src/test/java/eu/dnetlib/dhp/schema/oaf/MergeTest.java index 6ee5b9d85..f5b9bf028 100644 --- a/dhp-schemas/src/test/java/eu/dnetlib/dhp/schema/oaf/MergeTest.java +++ b/dhp-schemas/src/test/java/eu/dnetlib/dhp/schema/oaf/MergeTest.java @@ -85,6 +85,11 @@ public class MergeTest { b = createRel(true, "2016-04-05T12:41:19.202Z"); a.mergeFrom(b); assertEquals("2016-04-05T12:41:19.202Z", a.getValidationDate()); + + a = createRel(true, "2016-05-07T12:41:19.202Z"); + b = createRel(true, "2016-04-05T12:41:19.202Z"); + a.mergeFrom(b); + assertEquals("2016-04-05T12:41:19.202Z", a.getValidationDate()); } @Test From 9cac6da9bd7ce73ecaf61dd6a7451deb80454ce5 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Fri, 12 Mar 2021 16:31:17 +0100 Subject: [PATCH 152/445] added AccessRight and OpenAccessRoute classes to ModelSupport.getOafModelClasses() --- .../main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java index a92e11b5a..d58576019 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java @@ -381,6 +381,8 @@ public class ModelSupport { Field.class, GeoLocation.class, Instance.class, + AccessRight.class, + OpenAccessRoute.class, Journal.class, KeyValue.class, Oaf.class, From 61a2551e74d18f35cfbfe3043e51899d478e3285 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 15 Mar 2021 17:17:55 +0100 Subject: [PATCH 153/445] migrated last changes from svn (dnet45) --- .../plugin/rest/RestCollectorPlugin.java | 19 +- .../collection/plugin/rest/RestIterator.java | 166 +++++++----------- 2 files changed, 83 insertions(+), 102 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/rest/RestCollectorPlugin.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/rest/RestCollectorPlugin.java index ad8bfa4ea..e59db143a 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/rest/RestCollectorPlugin.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/rest/RestCollectorPlugin.java @@ -1,6 +1,7 @@ package eu.dnetlib.dhp.collection.plugin.rest; +import java.util.Optional; import java.util.Spliterator; import java.util.Spliterators; import java.util.stream.Stream; @@ -23,6 +24,8 @@ import eu.dnetlib.dhp.collection.plugin.CollectorPlugin; */ public class RestCollectorPlugin implements CollectorPlugin { + public static final String RESULT_SIZE_VALUE_DEFAULT = "100"; + private HttpClientParams clientParams; public RestCollectorPlugin(HttpClientParams clientParams) { @@ -32,6 +35,7 @@ public class RestCollectorPlugin implements CollectorPlugin { @Override public Stream collect(final ApiDescriptor api, final AggregatorReport report) throws CollectorException { final String baseUrl = api.getBaseUrl(); + final String resumptionType = api.getParams().get("resumptionType"); final String resumptionParam = api.getParams().get("resumptionParam"); final String resumptionXpath = api.getParams().get("resumptionXpath"); @@ -39,12 +43,14 @@ public class RestCollectorPlugin implements CollectorPlugin { final String resultFormatParam = api.getParams().get("resultFormatParam"); final String resultFormatValue = api.getParams().get("resultFormatValue"); final String resultSizeParam = api.getParams().get("resultSizeParam"); - final String resultSizeValue = (StringUtils.isBlank(api.getParams().get("resultSizeValue"))) ? "100" - : api.getParams().get("resultSizeValue"); final String queryParams = api.getParams().get("queryParams"); final String entityXpath = api.getParams().get("entityXpath"); final String authMethod = api.getParams().get("authMethod"); final String authToken = api.getParams().get("authToken"); + final String resultSizeValue = Optional + .ofNullable(api.getParams().get("resultSizeValue")) + .filter(StringUtils::isNotBlank) + .orElse(RESULT_SIZE_VALUE_DEFAULT); if (StringUtils.isBlank(baseUrl)) { throw new CollectorException("Param 'baseUrl' is null or empty"); @@ -65,6 +71,12 @@ public class RestCollectorPlugin implements CollectorPlugin { throw new CollectorException("Param 'entityXpath' is null or empty"); } + final String resultOutputFormat = Optional + .ofNullable(api.getParams().get("resultOutputFormat")) + .map(String::toLowerCase) + .filter(StringUtils::isNotBlank) + .orElse(resultFormatValue.toLowerCase()); + RestIterator it = new RestIterator( getClientParams(), baseUrl, @@ -79,7 +91,8 @@ public class RestCollectorPlugin implements CollectorPlugin { queryParams, entityXpath, authMethod, - authToken); + authToken, + resultOutputFormat); return StreamSupport .stream( diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/rest/RestIterator.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/rest/RestIterator.java index b728293d5..fdefa67b8 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/rest/RestIterator.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/collection/plugin/rest/RestIterator.java @@ -1,6 +1,27 @@ package eu.dnetlib.dhp.collection.plugin.rest; +import eu.dnetlib.dhp.collection.CollectorException; +import eu.dnetlib.dhp.collection.HttpClientParams; +import eu.dnetlib.dhp.collection.JsonUtils; +import org.apache.avro.test.http.Http; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpHeaders; +import org.apache.http.entity.ContentType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.*; import java.io.InputStream; import java.io.StringWriter; import java.io.UnsupportedEncodingException; @@ -12,30 +33,8 @@ import java.util.Iterator; import java.util.Queue; import java.util.concurrent.PriorityBlockingQueue; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerConfigurationException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; -import javax.xml.xpath.*; - -import org.apache.commons.httpclient.HttpMethod; -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.apache.http.HttpHeaders; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; - -import eu.dnetlib.dhp.collection.CollectorException; -import eu.dnetlib.dhp.collection.HttpClientParams; -import eu.dnetlib.dhp.collection.JsonUtils; - /** - * log.debug(...) equal to log.trace(...) in the application-logs + * log.info(...) equal to log.trace(...) in the application-logs *

* known bug: at resumptionType 'discover' if the (resultTotal % resultSizeValue) == 0 the collecting fails -> change the resultSizeValue * @@ -45,7 +44,8 @@ import eu.dnetlib.dhp.collection.JsonUtils; */ public class RestIterator implements Iterator { - private static final Log log = LogFactory.getLog(RestIterator.class); + private static final Logger log = LoggerFactory.getLogger(RestIterator.class); + public static final String UTF_8 = "UTF-8"; private HttpClientParams clientParams; @@ -74,65 +74,15 @@ public class RestIterator implements Iterator { private String querySize; private String authMethod; private String authToken; - private final Queue recordQueue = new PriorityBlockingQueue(); + private Queue recordQueue = new PriorityBlockingQueue(); private int discoverResultSize = 0; private int pagination = 1; - - /** - * RestIterator class - * - * compatible to version before 1.3.33 - * - * @param baseUrl - * @param resumptionType - * @param resumptionParam - * @param resumptionXpath - * @param resultTotalXpath - * @param resultFormatParam - * @param resultFormatValue - * @param resultSizeParam - * @param resultSizeValueStr - * @param queryParams - * @param entityXpath + /* + * While resultFormatValue is added to the request parameter, this is used to say that the results are retrieved in + * json. useful for cases when the target API expects a resultFormatValue != json, but the results are returned in + * json. An example is the EU Open Data Portal API: resultFormatValue=standard, results are in json format. */ - public RestIterator( - final HttpClientParams clientParams, - final String baseUrl, - final String resumptionType, - final String resumptionParam, - final String resumptionXpath, - final String resultTotalXpath, - final String resultFormatParam, - final String resultFormatValue, - final String resultSizeParam, - final String resultSizeValueStr, - final String queryParams, - final String entityXpath) { - this(clientParams, baseUrl, resumptionType, resumptionParam, resumptionXpath, resultTotalXpath, - resultFormatParam, resultFormatValue, resultSizeParam, resultSizeValueStr, queryParams, entityXpath, "", - ""); - } - - public RestIterator( - final HttpClientParams clientParams, - final String baseUrl, - final String resumptionType, - final String resumptionParam, - final String resumptionXpath, - final String resultTotalXpath, - final String resultFormatParam, - final String resultFormatValue, - final String resultSizeParam, - final String resultSizeValueStr, - final String queryParams, - final String entityXpath, - final String authMethod, - final String authToken, - final String resultOffsetParam) { - this(clientParams, baseUrl, resumptionType, resumptionParam, resumptionXpath, resultTotalXpath, - resultFormatParam, resultFormatValue, resultSizeParam, resultSizeValueStr, queryParams, entityXpath, "", - ""); - } + private String resultOutputFormat; /** RestIterator class * compatible to version 1.3.33 @@ -151,17 +101,20 @@ public class RestIterator implements Iterator { final String queryParams, final String entityXpath, final String authMethod, - final String authToken) { + final String authToken, + final String resultOutputFormat) { + this.clientParams = clientParams; this.jsonUtils = new JsonUtils(); this.baseUrl = baseUrl; this.resumptionType = resumptionType; this.resumptionParam = resumptionParam; this.resultFormatValue = resultFormatValue; - this.queryParams = queryParams; this.resultSizeValue = Integer.valueOf(resultSizeValueStr); + this.queryParams = queryParams; this.authMethod = authMethod; this.authToken = authToken; + this.resultOutputFormat = resultOutputFormat; queryFormat = StringUtils.isNotBlank(resultFormatParam) ? "&" + resultFormatParam + "=" + resultFormatValue : ""; @@ -188,6 +141,7 @@ public class RestIterator implements Iterator { private void initQueue() { query = baseUrl + "?" + queryParams + querySize + queryFormat; + log.info("REST calls starting with " + query); } private void disconnect() { @@ -217,9 +171,7 @@ public class RestIterator implements Iterator { synchronized (recordQueue) { while (recordQueue.isEmpty() && !query.isEmpty()) { try { - log.debug("get Query: " + query); query = downloadPage(query); - log.debug("next queryURL from downloadPage(): " + query); } catch (CollectorException e) { log.debug("CollectorPlugin.next()-Exception: " + e); throw new RuntimeException(e); @@ -235,9 +187,12 @@ public class RestIterator implements Iterator { private String downloadPage(String query) throws CollectorException { String resultJson; String resultXml = ""; + String nextQuery = ""; String emptyXml = resultXml + "<" + JsonUtils.wrapName + ">"; Node resultNode = null; NodeList nodeList = null; + String qUrlArgument = ""; + int urlOldResumptionSize = 0; InputStream theHttpInputStream; // check if cursor=* is initial set otherwise add it to the queryParam URL @@ -249,20 +204,22 @@ public class RestIterator implements Iterator { } try { + log.info("requestig URL [{}]", query); + URL qUrl = new URL(query); log.debug("authMethod :" + authMethod); - if (this.authMethod == "bearer") { + if ("bearer".equalsIgnoreCase(this.authMethod)) { log.trace("authMethod before inputStream: " + resultXml); HttpURLConnection conn = (HttpURLConnection) qUrl.openConnection(); conn.setRequestProperty(HttpHeaders.AUTHORIZATION, "Bearer " + authToken); - conn.setRequestProperty(HttpHeaders.CONTENT_TYPE, "application/json"); + conn.setRequestProperty(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()); conn.setRequestMethod("GET"); theHttpInputStream = conn.getInputStream(); } else if (BASIC.equalsIgnoreCase(this.authMethod)) { log.trace("authMethod before inputStream: " + resultXml); HttpURLConnection conn = (HttpURLConnection) qUrl.openConnection(); conn.setRequestProperty(HttpHeaders.AUTHORIZATION, "Basic " + authToken); - conn.setRequestProperty(HttpHeaders.ACCEPT, "application/xml"); + conn.setRequestProperty(HttpHeaders.ACCEPT, ContentType.APPLICATION_XML.getMimeType()); conn.setRequestMethod("GET"); theHttpInputStream = conn.getInputStream(); } else { @@ -270,10 +227,10 @@ public class RestIterator implements Iterator { } resultStream = theHttpInputStream; - if ("json".equalsIgnoreCase(resultFormatValue)) { - resultJson = IOUtils.toString(resultStream, "UTF-8"); + if ("json".equals(resultOutputFormat)) { + resultJson = IOUtils.toString(resultStream, UTF_8); resultXml = jsonUtils.convertToXML(resultJson); - resultStream = IOUtils.toInputStream(resultXml, "UTF-8"); + resultStream = IOUtils.toInputStream(resultXml, UTF_8); } if (!(emptyXml).equalsIgnoreCase(resultXml)) { @@ -283,15 +240,19 @@ public class RestIterator implements Iterator { for (int i = 0; i < nodeList.getLength(); i++) { StringWriter sw = new StringWriter(); transformer.transform(new DOMSource(nodeList.item(i)), new StreamResult(sw)); - recordQueue.add(sw.toString()); + String toEnqueue = sw.toString(); + if (toEnqueue == null || StringUtils.isBlank(toEnqueue) || emptyXml.equalsIgnoreCase(toEnqueue)) { + log.warn("The following record resulted in empty item for the feeding queue: " + resultXml); + } else { + recordQueue.add(sw.toString()); + } } } else { - log.info("resultXml is equal with emptyXml"); + log.warn("resultXml is equal with emptyXml"); } resumptionInt += resultSizeValue; - String qUrlArgument = ""; switch (resumptionType.toLowerCase()) { case "scan": // read of resumptionToken , evaluate next results, e.g. OAI, iterate over items resumptionStr = xprResumptionPath.evaluate(resultNode); @@ -307,7 +268,6 @@ public class RestIterator implements Iterator { } qUrlArgument = qUrl.getQuery(); String[] arrayQUrlArgument = qUrlArgument.split("&"); - int urlOldResumptionSize = 0; for (String arrayUrlArgStr : arrayQUrlArgument) { if (arrayUrlArgStr.startsWith(resumptionParam)) { String[] resumptionKeyValue = arrayUrlArgStr.split("="); @@ -334,7 +294,7 @@ public class RestIterator implements Iterator { discoverResultSize += nodeList.getLength(); } } - log.debug("discoverResultSize: " + discoverResultSize); + log.info("discoverResultSize: {}", discoverResultSize); break; case "pagination": @@ -384,25 +344,24 @@ public class RestIterator implements Iterator { } } catch (Exception e) { - log.error(e); + log.error(e.getMessage(), e); throw new IllegalStateException("collection failed: " + e.getMessage()); } try { if (resultTotal == -1) { resultTotal = Integer.parseInt(xprResultTotalPath.evaluate(resultNode)); - if (resumptionType.toLowerCase().equals("page") && !BASIC.equalsIgnoreCase(authMethod)) { + if (resumptionType.equalsIgnoreCase("page") && !BASIC.equalsIgnoreCase(authMethod)) { resultTotal += 1; } // to correct the upper bound log.info("resultTotal was -1 is now: " + resultTotal); } } catch (Exception e) { - log.error(e); + log.error(e.getMessage(), e); throw new IllegalStateException("downloadPage resultTotal couldn't parse: " + e.getMessage()); } log.debug("resultTotal: " + resultTotal); log.debug("resInt: " + resumptionInt); - String nextQuery; if (resumptionInt <= resultTotal) { nextQuery = baseUrl + "?" + queryParams + querySize + "&" + resumptionParam + "=" + resumptionStr + queryFormat; @@ -413,6 +372,7 @@ public class RestIterator implements Iterator { } log.debug("nextQueryUrl: " + nextQuery); return nextQuery; + } private boolean isInteger(String s) { @@ -439,4 +399,12 @@ public class RestIterator implements Iterator { } } + public String getResultFormatValue() { + return resultFormatValue; + } + + public String getResultOutputFormat() { + return resultOutputFormat; + } + } From 640b88570694e41415a3006bac5ccbd6eb0f9868 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 16 Mar 2021 14:19:32 +0100 Subject: [PATCH 154/445] added instance.alternativeIdentifiers to the graph model, adjusted the mapping applied to the contents from the aggregator --- .../schema/oaf/utils/IdentifierFactory.java | 74 +++++++++------- .../dhp/schema/common/ModelConstants.java | 2 +- .../eu/dnetlib/dhp/schema/oaf/Instance.java | 11 +++ .../dhp/schema/oaf/StructuredProperty.java | 14 ++- .../raw/AbstractMdRecordToOafMapper.java | 4 +- .../dhp/oa/graph/raw/OafToOafMapper.java | 15 +++- .../dhp/oa/graph/raw/OdfToOafMapper.java | 14 ++- .../dnetlib/dhp/oa/graph/raw/MappersTest.java | 87 +++++++++++++++++-- .../dhp/oa/graph/raw/oaf_record_pubmed.xml | 64 ++++++++++++++ 9 files changed, 242 insertions(+), 43 deletions(-) create mode 100644 dhp-workflows/dhp-graph-mapper/src/test/resources/eu/dnetlib/dhp/oa/graph/raw/oaf_record_pubmed.xml diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 40279d29d..a24e40c5f 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -10,8 +10,10 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; import com.google.common.collect.HashBiMap; +import com.google.common.collect.Lists; import com.google.common.collect.Maps; import eu.dnetlib.dhp.schema.oaf.*; @@ -54,6 +56,10 @@ public class IdentifierFactory implements Serializable { PID_AUTHORITY.get(PidType.arXiv).put(ARXIV_ID, "arXiv.org e-Print Archive"); } + public static List getPids(List pid, KeyValue collectedFrom) { + return pidFromInstance(pid, collectedFrom).collect(Collectors.toList()); + } + /** * Creates an identifier from the most relevant PID (if available) provided by a known PID authority in the given * entity T. Returns entity.id when none of the PIDs meet the selection criteria is available. @@ -91,37 +97,7 @@ public class IdentifierFactory implements Serializable { return Optional .ofNullable(((Result) entity).getInstance()) .map( - instance -> instance - .stream() - .map( - i -> Optional - .ofNullable(i.getPid()) - .map( - pp -> pp - .stream() - // filter away PIDs provided by a DS that is not considered an authority for the - // given PID Type - .filter(p -> { - final PidType pType = PidType.tryValueOf(p.getQualifier().getClassid()); - return Optional.ofNullable(i.getCollectedfrom()).isPresent() && - Optional - .ofNullable(PID_AUTHORITY.get(pType)) - .map(authorities -> { - final KeyValue cf = i.getCollectedfrom(); - return authorities.containsKey(cf.getKey()) - || authorities.containsValue(cf.getValue()); - }) - .orElse(false); - }) - .map(CleaningFunctions::normalizePidValue) - .filter(IdentifierFactory::pidFilter)) - .orElse(Stream.empty())) - .flatMap(Function.identity()) - .collect( - Collectors - .groupingBy( - p -> p.getQualifier().getClassid(), - Collectors.mapping(p -> p, Collectors.toList())))) + instance -> mapPids(instance)) .orElse(new HashMap<>()); } else { return entity @@ -137,6 +113,42 @@ public class IdentifierFactory implements Serializable { } } + private static Map> mapPids(List instance) { + return instance + .stream() + .map(i -> pidFromInstance(i.getPid(), i.getCollectedfrom())) + .flatMap(Function.identity()) + .collect( + Collectors + .groupingBy( + p -> p.getQualifier().getClassid(), + Collectors.mapping(p -> p, Collectors.toList()))); + } + + private static Stream pidFromInstance(List pid, KeyValue collectedFrom) { + return Optional + .ofNullable(pid) + .map( + pp -> pp + .stream() + // filter away PIDs provided by a DS that is not considered an authority for the + // given PID Type + .filter(p -> { + final PidType pType = PidType.tryValueOf(p.getQualifier().getClassid()); + return Optional.ofNullable(collectedFrom).isPresent() && + Optional + .ofNullable(PID_AUTHORITY.get(pType)) + .map(authorities -> { + return authorities.containsKey(collectedFrom.getKey()) + || authorities.containsValue(collectedFrom.getValue()); + }) + .orElse(false); + }) + .map(CleaningFunctions::normalizePidValue) + .filter(IdentifierFactory::pidFilter)) + .orElse(Stream.empty()); + } + /** * @see {@link IdentifierFactory#createIdentifier(OafEntity, boolean)} */ diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java index 1a0117fb9..6c4de6342 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java @@ -18,7 +18,7 @@ public class ModelConstants { public static final String PUBMED_CENTRAL_ID = "10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357"; public static final String ARXIV_ID = "10|opendoar____::6f4922f45568161a8cdf4ad2299f6d23"; - //VOCABULARY VALUE + // VOCABULARY VALUE public static final String ACCESS_RIGHT_OPEN = "OPEN"; public static final String DNET_SUBJECT_TYPOLOGIES = "dnet:subject_classification_typologies"; diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Instance.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Instance.java index aae5695df..c4cde0c2a 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Instance.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Instance.java @@ -23,6 +23,8 @@ public class Instance implements Serializable { private List pid; + private List alternateIdentifier; + private Field dateofacceptance; // ( article | book ) processing charges. Defined here to cope with possible wrongly typed @@ -107,6 +109,14 @@ public class Instance implements Serializable { this.dateofacceptance = dateofacceptance; } + public List getAlternateIdentifier() { + return alternateIdentifier; + } + + public void setAlternateIdentifier(List alternateIdentifier) { + this.alternateIdentifier = alternateIdentifier; + } + public Field getProcessingchargeamount() { return processingchargeamount; } @@ -159,4 +169,5 @@ public class Instance implements Serializable { return toComparableString().equals(other.toComparableString()); } + } diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/StructuredProperty.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/StructuredProperty.java index 1fa0de0be..024b915e4 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/StructuredProperty.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/StructuredProperty.java @@ -2,6 +2,13 @@ package eu.dnetlib.dhp.schema.oaf; import java.io.Serializable; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.lang3.StringUtils; + +import com.google.common.base.Joiner; public class StructuredProperty implements Serializable { @@ -36,7 +43,12 @@ public class StructuredProperty implements Serializable { } public String toComparableString() { - return value != null ? value.toLowerCase() : ""; + return Stream + .of( + getQualifier().toComparableString(), + Optional.ofNullable(getValue()).map(String::toLowerCase).orElse("")) + .filter(StringUtils::isNotBlank) + .collect(Collectors.joining("||")); } @Override diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java index d56971220..bf07de116 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java @@ -284,8 +284,8 @@ public abstract class AbstractMdRecordToOafMapper { r.setCollectedfrom(Arrays.asList(collectedFrom)); r.setPid(prepareResultPids(doc, info)); - r.setDateofcollection(doc.valueOf("//dr:dateOfCollection|//dri:dateOfCollection")); - r.setDateoftransformation(doc.valueOf("//dr:dateOfTransformation|//dri:dateOfTransformation")); + r.setDateofcollection(doc.valueOf("//dr:dateOfCollection/text()|//dri:dateOfCollection/text()")); + r.setDateoftransformation(doc.valueOf("//dr:dateOfTransformation/text()|//dri:dateOfTransformation/text()")); r.setExtraInfo(new ArrayList<>()); // NOT PRESENT IN MDSTORES r.setOaiprovenance(prepareOAIprovenance(doc)); r.setAuthor(prepareAuthors(doc, info)); diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java index ccb3d6caf..22bd6718a 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OafToOafMapper.java @@ -5,7 +5,9 @@ import static eu.dnetlib.dhp.schema.common.ModelConstants.*; import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.*; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; @@ -19,6 +21,7 @@ import eu.dnetlib.dhp.common.PacePerson; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.dhp.schema.oaf.CleaningFunctions; +import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory; public class OafToOafMapper extends AbstractMdRecordToOafMapper { @@ -125,7 +128,17 @@ public class OafToOafMapper extends AbstractMdRecordToOafMapper { .setInstancetype(prepareQualifier(doc, "//dr:CobjCategory", DNET_PUBLICATION_RESOURCE)); instance.setCollectedfrom(collectedfrom); instance.setHostedby(hostedby); - instance.setPid(prepareResultPids(doc, info)); + + final List alternateIdentifier = prepareResultPids(doc, info); + final List pid = IdentifierFactory.getPids(alternateIdentifier, collectedfrom); + + final Set pids = pid.stream().collect(Collectors.toCollection(HashSet::new)); + + instance + .setAlternateIdentifier( + alternateIdentifier.stream().filter(i -> !pids.contains(i)).collect(Collectors.toList())); + instance.setPid(pid); + instance.setDateofacceptance(field(doc.valueOf("//oaf:dateAccepted"), info)); instance.setDistributionlocation(doc.valueOf("//oaf:distributionlocation")); instance diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java index 9d0d2368a..d4997cd2b 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java @@ -5,6 +5,7 @@ import static eu.dnetlib.dhp.schema.common.ModelConstants.*; import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.*; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; @@ -14,6 +15,7 @@ import org.dom4j.Node; import eu.dnetlib.dhp.common.PacePerson; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; import eu.dnetlib.dhp.schema.oaf.*; +import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory; public class OdfToOafMapper extends AbstractMdRecordToOafMapper { @@ -102,7 +104,17 @@ public class OdfToOafMapper extends AbstractMdRecordToOafMapper { .setInstancetype(prepareQualifier(doc, "//dr:CobjCategory", DNET_PUBLICATION_RESOURCE)); instance.setCollectedfrom(collectedfrom); instance.setHostedby(hostedby); - instance.setPid(prepareResultPids(doc, info)); + + final List alternateIdentifier = prepareResultPids(doc, info); + final List pid = IdentifierFactory.getPids(alternateIdentifier, collectedfrom); + + final Set pids = pid.stream().collect(Collectors.toCollection(HashSet::new)); + + instance + .setAlternateIdentifier( + alternateIdentifier.stream().filter(i -> !pids.contains(i)).collect(Collectors.toList())); + instance.setPid(pid); + instance.setDateofacceptance(field(doc.valueOf("//oaf:dateAccepted"), info)); instance.setDistributionlocation(doc.valueOf("//oaf:distributionlocation")); instance diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java index fb79610c8..b649f8026 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java @@ -123,9 +123,11 @@ public class MappersTest { }); assertEquals("0001", p.getInstance().get(0).getRefereed().getClassid()); assertNotNull(p.getInstance().get(0).getPid()); - assertTrue(p.getInstance().get(0).getPid().size() == 1); - assertEquals("doi", p.getInstance().get(0).getPid().get(0).getQualifier().getClassid()); - assertEquals("10.3897/oneeco.2.e13718", p.getInstance().get(0).getPid().get(0).getValue()); + assertTrue(p.getInstance().get(0).getPid().isEmpty()); + + assertTrue(!p.getInstance().get(0).getAlternateIdentifier().isEmpty()); + assertEquals("doi", p.getInstance().get(0).getAlternateIdentifier().get(0).getQualifier().getClassid()); + assertEquals("10.3897/oneeco.2.e13718", p.getInstance().get(0).getAlternateIdentifier().get(0).getValue()); assertNotNull(p.getBestaccessright()); assertEquals("OPEN", p.getBestaccessright().getClassid()); @@ -154,6 +156,78 @@ public class MappersTest { // System.out.println(new ObjectMapper().writeValueAsString(r2)); } + @Test + void testPublication_PubMed() throws IOException { + + final String xml = IOUtils.toString(getClass().getResourceAsStream("oaf_record_pubmed.xml")); + + final List list = new OafToOafMapper(vocs, false, true).processMdRecord(xml); + + assertEquals(1, list.size()); + assertTrue(list.get(0) instanceof Publication); + + final Publication p = (Publication) list.get(0); + + assertValidId(p.getId()); + + assertEquals(2, p.getOriginalId().size()); + assertTrue(p.getOriginalId().contains("oai:pubmedcentral.nih.gov:1517292")); + + assertValidId(p.getCollectedfrom().get(0).getKey()); + assertTrue(StringUtils.isNotBlank(p.getTitle().get(0).getValue())); + assertFalse(p.getDataInfo().getInvisible()); + assertTrue(StringUtils.isNotBlank(p.getDateofcollection())); + assertTrue(StringUtils.isNotBlank(p.getDateoftransformation())); + + assertTrue(p.getAuthor().size() > 0); + final Optional author = p + .getAuthor() + .stream() + .filter(a -> a.getPid() != null && !a.getPid().isEmpty()) + .findFirst(); + assertTrue(author.isPresent()); + + final StructuredProperty pid = author + .get() + .getPid() + .stream() + .findFirst() + .get(); + assertEquals("0000-0001-6651-1178", pid.getValue()); + assertEquals("ORCID", pid.getQualifier().getClassid()); + assertEquals("Open Researcher and Contributor ID", pid.getQualifier().getClassname()); + assertEquals(ModelConstants.DNET_PID_TYPES, pid.getQualifier().getSchemeid()); + assertEquals(ModelConstants.DNET_PID_TYPES, pid.getQualifier().getSchemename()); + assertEquals("Votsi,Nefta", author.get().getFullname()); + assertEquals("Votsi", author.get().getSurname()); + assertEquals("Nefta", author.get().getName()); + + assertTrue(p.getSubject().size() > 0); + assertTrue(p.getPid().size() > 0); + assertEquals(p.getPid().get(0).getValue(), "PMC1517292"); + assertEquals(p.getPid().get(0).getQualifier().getClassid(), "pmc"); + + assertNotNull(p.getInstance()); + assertTrue(p.getInstance().size() > 0); + p + .getInstance() + .stream() + .forEach(i -> { + assertNotNull(i.getAccessright()); + assertEquals("OPEN", i.getAccessright().getClassid()); + }); + assertEquals("UNKNOWN", p.getInstance().get(0).getRefereed().getClassid()); + assertNotNull(p.getInstance().get(0).getPid()); + assertTrue(p.getInstance().get(0).getPid().size() == 2); + + assertTrue(p.getInstance().get(0).getAlternateIdentifier().size() == 1); + assertEquals("doi", p.getInstance().get(0).getAlternateIdentifier().get(0).getQualifier().getClassid()); + assertEquals("10.3897/oneeco.2.e13718", p.getInstance().get(0).getAlternateIdentifier().get(0).getValue()); + + assertNotNull(p.getBestaccessright()); + assertEquals("OPEN", p.getBestaccessright().getClassid()); + } + @Test void testPublicationInvisible() throws IOException { @@ -239,9 +313,10 @@ public class MappersTest { }); assertEquals("0001", d.getInstance().get(0).getRefereed().getClassid()); assertNotNull(d.getInstance().get(0).getPid()); - assertTrue(d.getInstance().get(0).getPid().size() == 1); - assertEquals("doi", d.getInstance().get(0).getPid().get(0).getQualifier().getClassid()); - assertEquals("10.5281/zenodo.3234526", d.getInstance().get(0).getPid().get(0).getValue()); + assertTrue(d.getInstance().get(0).getPid().isEmpty()); + + assertEquals("doi", d.getInstance().get(0).getAlternateIdentifier().get(0).getQualifier().getClassid()); + assertEquals("10.5281/zenodo.3234526", d.getInstance().get(0).getAlternateIdentifier().get(0).getValue()); assertValidId(r1.getSource()); assertValidId(r1.getTarget()); diff --git a/dhp-workflows/dhp-graph-mapper/src/test/resources/eu/dnetlib/dhp/oa/graph/raw/oaf_record_pubmed.xml b/dhp-workflows/dhp-graph-mapper/src/test/resources/eu/dnetlib/dhp/oa/graph/raw/oaf_record_pubmed.xml new file mode 100644 index 000000000..241bfa4ae --- /dev/null +++ b/dhp-workflows/dhp-graph-mapper/src/test/resources/eu/dnetlib/dhp/oa/graph/raw/oaf_record_pubmed.xml @@ -0,0 +1,64 @@ + + +

+ od_______267::0000072375bc0e68fa09d4e6b7658248 + oai:pubmedcentral.nih.gov:1517292 + + + + + + 2020-08-03T18:38:58Z + 2020-08-03T19:38:58Z + od_______267 +
+ + DEATHS + Nikolaidou,Charitini + Votsi,Nefta + Sgardelis,Steanos + Halley,John + Pantis,John + Tsiafouli,Maria + 1922-07 + + https://europepmc.org/articles/PMC1517292/ + eng + Articles + Text + 0038 + + 1922-07-01 + opendoar____::267 + OPEN + + + PMC1517292 + 18738762 + 10.3897/oneeco.2.e13718 + + + + + https://www.ncbi.nlm.nih.gov/pmc/oai/oai.cgi + oai:pubmedcentral.nih.gov:1517292 + 2006-08-14 + http://www.openarchives.org/OAI/2.0/oai_dc/ + + + + false + false + 0.9 + + + + + \ No newline at end of file From 3b2da86f0a6207af58779b7a2619e670b22681ae Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 16 Mar 2021 17:05:38 +0100 Subject: [PATCH 155/445] added precondition on IdentifierFactory to check the presence of entity.id --- .../eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index a24e40c5f..6eb03f9e9 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -1,6 +1,7 @@ package eu.dnetlib.dhp.schema.oaf.utils; +import static com.google.common.base.Preconditions.checkArgument; import static eu.dnetlib.dhp.schema.common.ModelConstants.*; import java.io.Serializable; @@ -10,10 +11,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; -import org.jetbrains.annotations.NotNull; import com.google.common.collect.HashBiMap; -import com.google.common.collect.Lists; import com.google.common.collect.Maps; import eu.dnetlib.dhp.schema.oaf.*; @@ -71,6 +70,8 @@ public class IdentifierFactory implements Serializable { */ public static String createIdentifier(T entity, boolean md5) { + checkArgument(StringUtils.isNoneBlank(entity.getId()), "missing entity identifier"); + final Map> pids = extractPids(entity); return pids From cc5bbafa5dcc1fee8626b3381892cf3f1e1f2aa3 Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Wed, 17 Mar 2021 12:12:56 +0100 Subject: [PATCH 156/445] some fix to make workflows runs --- .../dhp/schema/common/ModelConstants.java | 2 +- .../doiboost/crossref/Crossref2Oaf.scala | 2 +- .../dnetlib/doiboost/mag/MagDataModel.scala | 2 +- .../doiboost/mag/SparkProcessMAG.scala | 2 +- .../dnetlib/doiboost/uw/UnpayWallToOAF.scala | 16 ++- .../dhp/doiboost/oozie_app/workflow.xml | 123 +++++++++++------- .../doiboost/uw/UnpayWallMappingTest.scala | 2 +- 7 files changed, 92 insertions(+), 57 deletions(-) diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java index 1a0117fb9..6c4de6342 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java @@ -18,7 +18,7 @@ public class ModelConstants { public static final String PUBMED_CENTRAL_ID = "10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357"; public static final String ARXIV_ID = "10|opendoar____::6f4922f45568161a8cdf4ad2299f6d23"; - //VOCABULARY VALUE + // VOCABULARY VALUE public static final String ACCESS_RIGHT_OPEN = "OPEN"; public static final String DNET_SUBJECT_TYPOLOGIES = "dnet:subject_classification_typologies"; diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala index 79194b283..2e30add3a 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala @@ -180,7 +180,7 @@ case object Crossref2Oaf { // Ticket #6281 added pid to Instance - instance.setPid(result.getPid.asScala.filter(p => p.getQualifier.getClassid.equalsIgnoreCase("doi")).asJava) + instance.setPid(result.getPid) val has_review = (json \ "relation" \"has-review" \ "id") diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala index 987a81fba..c4b28505f 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala @@ -172,7 +172,7 @@ case object ConversionUtil { i.setUrl(List(s"https://academic.microsoft.com/#/detail/${extractMagIdentifier(pub.getOriginalId.asScala)}").asJava) // Ticket #6281 added pid to Instance - i.setPid(pub.getPid.asScala.filter(p => p.getQualifier.getClassid.equalsIgnoreCase("doi")).asJava) + i.setPid(pub.getPid) i.setCollectedfrom(createMAGCollectedFrom()) pub.setInstance(List(i).asJava) diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/SparkProcessMAG.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/SparkProcessMAG.scala index bc1982e77..173e33360 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/SparkProcessMAG.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/SparkProcessMAG.scala @@ -67,7 +67,7 @@ object SparkProcessMAG { MagPaperAuthorDenormalized(mpa.PaperId, mpa.author, af.DisplayName, mpa.sequenceNumber) } else mpa - }).groupBy("PaperId").agg(collect_list(struct($"author", $"affiliation")).as("authors")) + }).groupBy("PaperId").agg(collect_list(struct($"author", $"affiliation", $"sequenceNumber")).as("authors")) .write.mode(SaveMode.Overwrite).save(s"$workingPath/merge_step_1_paper_authors") logger.info("Phase 4) create First Version of publication Entity with Paper Journal and Authors") diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala index 0972d55de..6e4b2400f 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala @@ -65,11 +65,7 @@ object UnpayWallToOAF { val colour = get_color(is_oa, oaLocation, journal_is_oa) pub.setPid(List(createSP(doi, "doi", PID_TYPES)).asJava) - //IMPORTANT - //The old method pub.setId(IdentifierFactory.createIdentifier(pub)) - //will be replaced using IdentifierFactory - //pub.setId(generateIdentifier(pub, doi.toLowerCase)) - pub.setId(IdentifierFactory.createIdentifier(pub)) + pub.setCollectedfrom(List(createUnpayWallCollectedFrom()).asJava) @@ -87,7 +83,7 @@ object UnpayWallToOAF { i.setUrl(List(oaLocation.url.get).asJava) // Ticket #6281 added pid to Instance - i.setPid(pub.getPid.asScala.filter(p => p.getQualifier.getClassid.equalsIgnoreCase("doi")).asJava) + i.setPid(pub.getPid) if (oaLocation.license.isDefined) i.setLicense(asField(oaLocation.license.get)) @@ -104,6 +100,14 @@ object UnpayWallToOAF { i.setAccessright(a) } pub.setInstance(List(i).asJava) + + //IMPORTANT + //The old method pub.setId(IdentifierFactory.createIdentifier(pub)) + //will be replaced using IdentifierFactory + //pub.setId(generateIdentifier(pub, doi.toLowerCase)) + val id = IdentifierFactory.createIdentifier(pub) + logger.info(id); + pub.setId(IdentifierFactory.createIdentifier(pub)) pub } diff --git a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/oozie_app/workflow.xml b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/oozie_app/workflow.xml index 3f5805b62..cc260d7c0 100644 --- a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/oozie_app/workflow.xml @@ -54,6 +54,11 @@ + + MAGDumpPath + the MAG dump working path + + inputPathMAG the MAG working path @@ -132,7 +137,10 @@ --executor-cores=${sparkExecutorCores} --driver-memory=${sparkDriverMemory} --conf spark.sql.shuffle.partitions=3840 - ${sparkExtraOPT} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} --workingPath${inputPathCrossref} --masteryarn-cluster @@ -147,6 +155,43 @@ + + + + + + + + + + + + + + + + + + + yarn-cluster + cluster + Convert Mag to Dataset + eu.dnetlib.doiboost.mag.SparkImportMagIntoDataset + dhp-doiboost-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.sql.shuffle.partitions=3840 + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + + --sourcePath${MAGDumpPath} + --targetPath${inputPathMAG}/dataset + --masteryarn-cluster + @@ -164,46 +209,15 @@ --executor-cores=${sparkExecutorCores} --driver-memory=${sparkDriverMemory} --conf spark.sql.shuffle.partitions=3840 - ${sparkExtraOPT} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} --sourcePath${inputPathCrossref}/crossref_ds --targetPath${workingPath} --masteryarn-cluster - - - - - - - - - - - - - - - - - - - - yarn-cluster - cluster - Convert Mag to Dataset - eu.dnetlib.doiboost.mag.SparkImportMagIntoDataset - dhp-doiboost-${projectVersion}.jar - - --executor-memory=${sparkExecutorMemory} - --executor-cores=${sparkExecutorCores} - --driver-memory=${sparkDriverMemory} - ${sparkExtraOPT} - - --sourcePath${inputPathMAG}/input - --targetPath${inputPathMAG}/dataset - --masteryarn-cluster - @@ -216,11 +230,14 @@ eu.dnetlib.doiboost.mag.SparkProcessMAG dhp-doiboost-${projectVersion}.jar - --executor-memory=${sparkExecutorMemory} + --executor-memory=${sparkExecutorIntersectionMemory} --executor-cores=${sparkExecutorCores} --driver-memory=${sparkDriverMemory} --conf spark.sql.shuffle.partitions=3840 - ${sparkExtraOPT} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} --sourcePath${inputPathMAG}/dataset --workingPath${inputPathMAG}/process @@ -245,10 +262,14 @@ --executor-cores=${sparkExecutorCores} --driver-memory=${sparkDriverMemory} --conf spark.sql.shuffle.partitions=3840 - ${sparkExtraOPT} + --conf spark.sql.shuffle.partitions=3840 + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} --sourcePath${inputPathUnpayWall}/uw_extracted - --targetPath${workingPath} + --targetPath${workingPath}/uwPublication --masteryarn-cluster @@ -268,10 +289,13 @@ --executor-cores=${sparkExecutorCores} --driver-memory=${sparkDriverMemory} --conf spark.sql.shuffle.partitions=3840 - ${sparkExtraOPT} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} --sourcePath${inputPathOrcid} - --targetPath${workingPath} + --targetPath${workingPath}/orcidPublication --masteryarn-cluster @@ -291,11 +315,15 @@ --executor-cores=${sparkExecutorCores} --driver-memory=${sparkDriverMemory} --conf spark.sql.shuffle.partitions=3840 - ${sparkExtraOPT} + --conf spark.sql.shuffle.partitions=3840 + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} --hostedByMapPath${hostedByMapPath} - --affiliationPath${inputPathMAG}/process/Affiliations - --paperAffiliationPath${inputPathMAG}/process/PaperAuthorAffiliations + --affiliationPath${inputPathMAG}/dataset/Affiliations + --paperAffiliationPath${inputPathMAG}/dataset/PaperAuthorAffiliations --workingPath${workingPath} --masteryarn-cluster @@ -316,7 +344,10 @@ --executor-cores=${sparkExecutorCores} --driver-memory=${sparkDriverMemory} --conf spark.sql.shuffle.partitions=3840 - ${sparkExtraOPT} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} --dbPublicationPath${workingPath}/doiBoostPublicationFiltered --dbDatasetPath${workingPath}/crossrefDataset diff --git a/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/uw/UnpayWallMappingTest.scala b/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/uw/UnpayWallMappingTest.scala index 3e4acdffb..94682d142 100644 --- a/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/uw/UnpayWallMappingTest.scala +++ b/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/uw/UnpayWallMappingTest.scala @@ -28,7 +28,7 @@ class UnpayWallMappingTest { if(p!= null) { assertTrue(p.getPid.size()==1) - logger.info(p.getId) + logger.info("ID :",p.getId) } assertNotNull(line) assertTrue(line.nonEmpty) From 8257f9a2bcc523fb2726e9e5a9f79a92d105a2de Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 17 Mar 2021 12:45:38 +0100 Subject: [PATCH 157/445] result.pid: adjusted the mapping applied to the contents from the aggregator --- .../schema/oaf/utils/IdentifierFactory.java | 2 +- .../dhp/schema/common/ModelConstants.java | 3 +++ .../raw/AbstractMdRecordToOafMapper.java | 17 +++---------- .../dnetlib/dhp/oa/graph/raw/MappersTest.java | 25 ++++++++++++------- 4 files changed, 23 insertions(+), 24 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 6eb03f9e9..84b261aff 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -56,7 +56,7 @@ public class IdentifierFactory implements Serializable { } public static List getPids(List pid, KeyValue collectedFrom) { - return pidFromInstance(pid, collectedFrom).collect(Collectors.toList()); + return pidFromInstance(pid, collectedFrom).distinct().collect(Collectors.toList()); } /** diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java index 6c4de6342..eaa8acef5 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java @@ -118,6 +118,9 @@ public class ModelConstants { public static final Qualifier UNKNOWN_COUNTRY = qualifier(UNKNOWN, "Unknown", DNET_COUNTRY_TYPE, DNET_COUNTRY_TYPE); + public static final Qualifier MAIN_TITLE_QUALIFIER = qualifier( + "main title", "main title", DNET_DATACITE_TITLE, DNET_DATACITE_TITLE); + private static Qualifier qualifier( final String classid, final String classname, diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java index bf07de116..3d75c426d 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/AbstractMdRecordToOafMapper.java @@ -4,13 +4,8 @@ package eu.dnetlib.dhp.oa.graph.raw; import static eu.dnetlib.dhp.schema.common.ModelConstants.*; import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.*; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.dom4j.Document; @@ -21,7 +16,6 @@ import org.dom4j.Node; import com.google.common.collect.Lists; import eu.dnetlib.dhp.oa.graph.raw.common.VocabularyGroup; -import eu.dnetlib.dhp.schema.common.AccessRightComparator; import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory; @@ -57,9 +51,6 @@ public abstract class AbstractMdRecordToOafMapper { nsContext.put("datacite", DATACITE_SCHEMA_KERNEL_3); } - protected static final Qualifier MAIN_TITLE_QUALIFIER = qualifier( - "main title", "main title", "dnet:dataCite_title", "dnet:dataCite_title"); - protected AbstractMdRecordToOafMapper(final VocabularyGroup vocs, final boolean invisible, final boolean shouldHashId) { this.vocs = vocs; @@ -279,11 +270,9 @@ public abstract class AbstractMdRecordToOafMapper { r.setDataInfo(info); r.setLastupdatetimestamp(lastUpdateTimestamp); r.setId(createOpenaireId(50, doc.valueOf("//dri:objIdentifier"), false)); - r.setOriginalId(Lists.newArrayList(findOriginalId(doc))); - r.setCollectedfrom(Arrays.asList(collectedFrom)); - r.setPid(prepareResultPids(doc, info)); + r.setPid(IdentifierFactory.getPids(prepareResultPids(doc, info), collectedFrom)); r.setDateofcollection(doc.valueOf("//dr:dateOfCollection/text()|//dri:dateOfCollection/text()")); r.setDateoftransformation(doc.valueOf("//dr:dateOfTransformation/text()|//dri:dateOfTransformation/text()")); r.setExtraInfo(new ArrayList<>()); // NOT PRESENT IN MDSTORES diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java index b649f8026..7500afd5a 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java @@ -108,9 +108,7 @@ public class MappersTest { assertTrue(StringUtils.isNotBlank(p.getJournal().getIssnOnline())); assertTrue(StringUtils.isNotBlank(p.getJournal().getName())); - assertTrue(p.getPid().size() > 0); - assertEquals(p.getPid().get(0).getValue(), "10.3897/oneeco.2.e13718"); - assertEquals(p.getPid().get(0).getQualifier().getClassid(), "doi"); + assertTrue(p.getPid().isEmpty()); assertNotNull(p.getInstance()); assertTrue(p.getInstance().size() > 0); @@ -398,7 +396,12 @@ public class MappersTest { assertEquals(1, d.getAuthor().size()); assertEquals(1, d.getSubject().size()); assertEquals(1, d.getInstance().size()); - assertEquals(1, d.getPid().size()); + assertTrue(d.getPid().isEmpty()); + + assertTrue(d.getInstance().get(0).getPid().isEmpty()); + assertEquals(1, d.getInstance().get(0).getAlternateIdentifier().size()); + assertEquals("handle", d.getInstance().get(0).getAlternateIdentifier().get(0).getQualifier().getClassid()); + assertNotNull(d.getInstance().get(0).getUrl()); } @@ -447,8 +450,8 @@ public class MappersTest { assertTrue(StringUtils.isNotBlank(p.getTitle().get(0).getValue())); assertEquals(1, p.getAuthor().size()); assertEquals("OPEN", p.getBestaccessright().getClassid()); - assertTrue(StringUtils.isNotBlank(p.getPid().get(0).getValue())); - assertTrue(StringUtils.isNotBlank(p.getPid().get(0).getQualifier().getClassid())); + + assertTrue(p.getPid().isEmpty()); assertEquals("dataset", p.getResulttype().getClassname()); assertEquals(1, p.getInstance().size()); assertEquals("OPEN", p.getInstance().get(0).getAccessright().getClassid()); @@ -456,10 +459,14 @@ public class MappersTest { assertValidId(p.getInstance().get(0).getHostedby().getKey()); assertEquals( "http://creativecommons.org/licenses/by/3.0/de/legalcode", p.getInstance().get(0).getLicense().getValue()); + + assertEquals(1, p.getInstance().size()); + assertEquals(1, p.getInstance().get(0).getAlternateIdentifier().size()); + assertEquals("handle", p.getInstance().get(0).getAlternateIdentifier().get(0).getQualifier().getClassid()); + assertEquals( + "hdl:11858/00-1734-0000-0003-EE73-2", p.getInstance().get(0).getAlternateIdentifier().get(0).getValue()); + assertEquals(1, p.getInstance().get(0).getUrl().size()); -// System.out.println(p.getInstance().get(0).getUrl().get(0)); -// System.out.println(p.getInstance().get(0).getHostedby().getValue()); - System.out.println(p.getPid().get(0).getValue()); } @Test From a3dac32f1670694afb0cfe53b463047dd3a8b0b3 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 17 Mar 2021 15:06:05 +0100 Subject: [PATCH 158/445] pidFilter a bit more permissive --- .../dhp/schema/oaf/utils/IdentifierFactory.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 84b261aff..4682b7aed 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -171,15 +171,7 @@ public class IdentifierFactory implements Serializable { if (PidBlacklistProvider.getBlacklist(s.getQualifier().getClassid()).contains(pidValue)) { return false; } - switch (PidType.tryValueOf(s.getQualifier().getClassid())) { - case doi: - final String doi = StringUtils.trim(StringUtils.lowerCase(pidValue)); - return doi.matches(DOI_REGEX); - case original: - return false; - default: - return true; - } + return true; } private static String idFromPid(T entity, StructuredProperty s, boolean md5) { From 734232d3b94a640321f675f2f72af4a9728a49ec Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 17 Mar 2021 15:14:53 +0100 Subject: [PATCH 159/445] identifier factory doesn't depend on pre-existing entity.id --- .../eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 4682b7aed..0371a2879 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -10,6 +10,7 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +import eu.dnetlib.dhp.schema.common.ModelSupport; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.HashBiMap; @@ -176,7 +177,7 @@ public class IdentifierFactory implements Serializable { private static String idFromPid(T entity, StructuredProperty s, boolean md5) { return new StringBuilder() - .append(StringUtils.substringBefore(entity.getId(), ID_PREFIX_SEPARATOR)) + .append(ModelSupport.getIdPrefix(entity.getClass())) .append(ID_PREFIX_SEPARATOR) .append(createPrefix(s.getQualifier().getClassid())) .append(ID_SEPARATOR) From 5f98ea74a95db718fc2199c4e6991f3534428d03 Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Wed, 17 Mar 2021 15:53:24 +0100 Subject: [PATCH 160/445] Added fix for pid generation in stableIds --- .../schema/oaf/utils/IdentifierFactory.java | 27 +++++++++++++++++++ .../doiboost/crossref/Crossref2Oaf.scala | 5 ++++ .../dnetlib/doiboost/mag/MagDataModel.scala | 4 +-- .../dnetlib/doiboost/orcid/ORCIDToOAF.scala | 7 ++++- .../dnetlib/doiboost/uw/UnpayWallToOAF.scala | 15 +++-------- .../doiboost/uw/UnpayWallMappingTest.scala | 6 ++--- 6 files changed, 45 insertions(+), 19 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 0371a2879..8b297079c 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -60,6 +60,33 @@ public class IdentifierFactory implements Serializable { return pidFromInstance(pid, collectedFrom).distinct().collect(Collectors.toList()); } + public static String createDOIBoostIdentifier(T entity) { + if (entity == null) + return null; + + StructuredProperty pid = null; + if(entity.getPid() != null ) { + pid = entity.getPid() + .stream() + .filter(Objects::nonNull) + .filter(s -> s.getQualifier()!= null && "doi".equalsIgnoreCase(s.getQualifier().getClassid())) + .filter(IdentifierFactory::pidFilter) + .findAny().orElse(null); + } else { + if (entity.getInstance()!= null) { + pid = entity.getInstance() + .stream() + .filter(i -> i.getPid()!= null) + .flatMap(i -> i.getPid().stream()) + .filter(IdentifierFactory::pidFilter) + .findAny().orElse(null); + } + } + if (pid!= null) + return idFromPid(entity, pid, true); + return null; + } + /** * Creates an identifier from the most relevant PID (if available) provided by a known PID authority in the given * entity T. Returns entity.id when none of the PIDs meet the selection criteria is available. diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala index 2e30add3a..43b3f7e1c 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala @@ -206,6 +206,9 @@ case object Crossref2Oaf { val links: List[String] = ((for {JString(url) <- json \ "link" \ "URL"} yield url) ::: List(s)).filter(p => p != null).distinct if (links.nonEmpty) instance.setUrl(links.asJava) + result.setId(IdentifierFactory.createDOIBoostIdentifier(result)) + if (result.getId== null) + return null result } @@ -240,6 +243,8 @@ case object Crossref2Oaf { return List() val cOBJCategory = mappingCrossrefSubType.getOrElse(objectType, mappingCrossrefSubType.getOrElse(objectSubType, "0038 Other literature type")); mappingResult(result, json, cOBJCategory) + if (result == null) + return List() val funderList: List[mappingFunder] = (json \ "funder").extractOrElse[List[mappingFunder]](List()) diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala index c4b28505f..6a8ae9928 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala @@ -197,8 +197,8 @@ case object ConversionUtil { //IMPORTANT //The old method result.setId(generateIdentifier(result, doi)) //will be replaced using IdentifierFactory - pub.setId(generateIdentifier(pub, paper.Doi.toLowerCase)) - pub.setId(IdentifierFactory.createIdentifier(pub)) + + pub.setId(IdentifierFactory.createDOIBoostIdentifier(pub)) val mainTitles = createSP(paper.PaperTitle, "main title", "dnet:dataCite_title") val originalTitles = createSP(paper.OriginalTitle, "alternative title", "dnet:dataCite_title") diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala index ccf005ce1..02016b47c 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala @@ -1,6 +1,7 @@ package eu.dnetlib.doiboost.orcid import com.fasterxml.jackson.databind.ObjectMapper +import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory import eu.dnetlib.dhp.schema.oaf.{Author, DataInfo, Publication} import eu.dnetlib.dhp.schema.orcid.OrcidDOI import eu.dnetlib.doiboost.DoiBoostMappingUtil @@ -49,7 +50,11 @@ object ORCIDToOAF { val pub:Publication = new Publication pub.setPid(List(createSP(doi.toLowerCase, "doi", PID_TYPES)).asJava) pub.setDataInfo(generateDataInfo()) - pub.setId(generateIdentifier(pub, doi.toLowerCase)) + + pub.setId(IdentifierFactory.createDOIBoostIdentifier(pub)) + if (pub.getId == null) + return null + try{ val l:List[Author]= input.getAuthors.asScala.map(a=> { diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala index 6e4b2400f..b9895dd09 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala @@ -55,7 +55,6 @@ object UnpayWallToOAF { val doi = (json \"doi").extract[String] - val is_oa = (json\ "is_oa").extract[Boolean] val journal_is_oa= (json\ "journal_is_oa").extract[Boolean] @@ -63,10 +62,6 @@ object UnpayWallToOAF { val oaLocation:OALocation = (json \ "best_oa_location").extractOrElse[OALocation](null) val colour = get_color(is_oa, oaLocation, journal_is_oa) - pub.setPid(List(createSP(doi, "doi", PID_TYPES)).asJava) - - - pub.setCollectedfrom(List(createUnpayWallCollectedFrom()).asJava) pub.setDataInfo(generateDataInfo()) @@ -82,12 +77,9 @@ object UnpayWallToOAF { // i.setAccessright(getOpenAccessQualifier()) i.setUrl(List(oaLocation.url.get).asJava) - // Ticket #6281 added pid to Instance - i.setPid(pub.getPid) - if (oaLocation.license.isDefined) i.setLicense(asField(oaLocation.license.get)) - + pub.setPid(List(createSP(doi, "doi", PID_TYPES)).asJava) // Ticket #6282 Adding open Access Colour if (colour.isDefined) { @@ -98,6 +90,7 @@ object UnpayWallToOAF { a.setSchemename(ModelConstants.DNET_ACCESS_MODES) a.setOpenAccessRoute(colour.get) i.setAccessright(a) + i.setPid(List(createSP(doi, "doi", PID_TYPES)).asJava) } pub.setInstance(List(i).asJava) @@ -105,9 +98,7 @@ object UnpayWallToOAF { //The old method pub.setId(IdentifierFactory.createIdentifier(pub)) //will be replaced using IdentifierFactory //pub.setId(generateIdentifier(pub, doi.toLowerCase)) - val id = IdentifierFactory.createIdentifier(pub) - logger.info(id); - pub.setId(IdentifierFactory.createIdentifier(pub)) + pub.setId(IdentifierFactory.createDOIBoostIdentifier(pub)) pub } diff --git a/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/uw/UnpayWallMappingTest.scala b/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/uw/UnpayWallMappingTest.scala index 94682d142..6688fc616 100644 --- a/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/uw/UnpayWallMappingTest.scala +++ b/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/uw/UnpayWallMappingTest.scala @@ -22,13 +22,11 @@ class UnpayWallMappingTest { for (line <-Ilist.lines) { - - val p = UnpayWallToOAF.convertToOAF(line) if(p!= null) { - assertTrue(p.getPid.size()==1) - logger.info("ID :",p.getId) + assertTrue(p.getInstance().size()==1) + logger.info(s"ID : ${p.getId}") } assertNotNull(line) assertTrue(line.nonEmpty) From 25d5663d971d0086f2f5b5ce6a64a8e6fb5702c7 Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Thu, 18 Mar 2021 10:24:42 +0100 Subject: [PATCH 161/445] added filter --- .../schema/oaf/utils/IdentifierFactory.java | 40 ++++++++++--------- .../doiboost/DoiBoostMappingUtil.scala | 2 + 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 8b297079c..0641afb83 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -10,12 +10,12 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import eu.dnetlib.dhp.schema.common.ModelSupport; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.HashBiMap; import com.google.common.collect.Maps; +import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.dhp.utils.DHPUtils; @@ -60,29 +60,33 @@ public class IdentifierFactory implements Serializable { return pidFromInstance(pid, collectedFrom).distinct().collect(Collectors.toList()); } - public static String createDOIBoostIdentifier(T entity) { + public static String createDOIBoostIdentifier(T entity) { if (entity == null) - return null; + return null; StructuredProperty pid = null; - if(entity.getPid() != null ) { - pid = entity.getPid() - .stream() - .filter(Objects::nonNull) - .filter(s -> s.getQualifier()!= null && "doi".equalsIgnoreCase(s.getQualifier().getClassid())) - .filter(IdentifierFactory::pidFilter) - .findAny().orElse(null); + if (entity.getPid() != null) { + pid = entity + .getPid() + .stream() + .filter(Objects::nonNull) + .filter(s -> s.getQualifier() != null && "doi".equalsIgnoreCase(s.getQualifier().getClassid())) + .filter(IdentifierFactory::pidFilter) + .findAny() + .orElse(null); } else { - if (entity.getInstance()!= null) { - pid = entity.getInstance() - .stream() - .filter(i -> i.getPid()!= null) - .flatMap(i -> i.getPid().stream()) - .filter(IdentifierFactory::pidFilter) - .findAny().orElse(null); + if (entity.getInstance() != null) { + pid = entity + .getInstance() + .stream() + .filter(i -> i.getPid() != null) + .flatMap(i -> i.getPid().stream()) + .filter(IdentifierFactory::pidFilter) + .findAny() + .orElse(null); } } - if (pid!= null) + if (pid != null) return idFromPid(entity, pid, true); return null; } diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala index efe8453a2..32c58595f 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala @@ -196,6 +196,8 @@ object DoiBoostMappingUtil { //Case empty publication if (publication == null) return false + if (publication.getId == null || publication.getId.isEmpty) + return false //Case publication with no title if (publication.getTitle == null || publication.getTitle.size == 0) From 972d5a3d98f5d270c30cb6b32a4d0b25520e50a9 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Fri, 19 Mar 2021 09:04:20 +0100 Subject: [PATCH 162/445] [dedup] Datacite should be authoritative for datasets --- .../java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java index 18f4f9b84..94a3bf613 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/Identifier.java @@ -112,11 +112,11 @@ public class Identifier implements Serializable, Comparable return 1; } if (getEntityType() == EntityType.dataset) { - if (isFromDatasourceID(lKeys, ModelConstants.CROSSREF_ID) - && !isFromDatasourceID(rKeys, ModelConstants.CROSSREF_ID)) + if (isFromDatasourceID(lKeys, ModelConstants.DATACITE_ID) + && !isFromDatasourceID(rKeys, ModelConstants.DATACITE_ID)) return -1; - if (isFromDatasourceID(rKeys, ModelConstants.CROSSREF_ID) - && !isFromDatasourceID(lKeys, ModelConstants.CROSSREF_ID)) + if (isFromDatasourceID(rKeys, ModelConstants.DATACITE_ID) + && !isFromDatasourceID(lKeys, ModelConstants.DATACITE_ID)) return 1; } From 9588bfba81af6d2a162c229cd1ee19da726ee134 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Fri, 19 Mar 2021 09:07:30 +0100 Subject: [PATCH 163/445] [cleaning] entries avaialbe as PIDs must not appear as alternateIdentifier --- .../dhp/schema/oaf/CleaningFunctions.java | 67 +++++++++++-------- .../schema/oaf/utils/IdentifierFactory.java | 42 +++++------- 2 files changed, 54 insertions(+), 55 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index 21bec2d2d..19b4c7618 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -5,6 +5,8 @@ import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; +import com.google.common.collect.Sets; +import eu.dnetlib.dhp.schema.oaf.utils.PidBlacklistProvider; import org.apache.commons.lang3.StringUtils; import com.clearspring.analytics.util.Lists; @@ -137,19 +139,7 @@ public class CleaningFunctions { .collect(Collectors.toList())); } if (Objects.nonNull(r.getPid())) { - r - .setPid( - r - .getPid() - .stream() - .filter(Objects::nonNull) - .filter(sp -> StringUtils.isNotBlank(StringUtils.trim(sp.getValue()))) - .filter(sp -> !PID_BLACKLIST.contains(sp.getValue().trim().toLowerCase())) - .filter(sp -> Objects.nonNull(sp.getQualifier())) - .filter(sp -> StringUtils.isNotBlank(sp.getQualifier().getClassid())) - .map(CleaningFunctions::normalizePidValue) - .filter(CleaningFunctions::filterPid) - .collect(Collectors.toList())); + r.setPid(processPidCleaning(r.getPid())); } if (Objects.isNull(r.getResourcetype()) || StringUtils.isBlank(r.getResourcetype().getClassid())) { r @@ -157,7 +147,18 @@ public class CleaningFunctions { qualifier(ModelConstants.UNKNOWN, "Unknown", ModelConstants.DNET_DATA_CITE_RESOURCE)); } if (Objects.nonNull(r.getInstance())) { + + + for (Instance i : r.getInstance()) { + final Set pids = Sets.newHashSet(i.getPid()); + i.setAlternateIdentifier( + Optional.ofNullable(i.getAlternateIdentifier()) + .map(altId -> altId.stream() + .filter(p -> !pids.contains(p)) + .collect(Collectors.toList())) + .orElse(Lists.newArrayList())); + if (Objects.isNull(i.getAccessright()) || StringUtils.isBlank(i.getAccessright().getClassid())) { i .setAccessright( @@ -234,6 +235,18 @@ public class CleaningFunctions { return value; } + private static List processPidCleaning(List pids) { + return pids.stream() + .filter(Objects::nonNull) + .filter(sp -> StringUtils.isNotBlank(StringUtils.trim(sp.getValue()))) + .filter(sp -> !PID_BLACKLIST.contains(sp.getValue().trim().toLowerCase())) + .filter(sp -> Objects.nonNull(sp.getQualifier())) + .filter(sp -> StringUtils.isNotBlank(sp.getQualifier().getClassid())) + .map(CleaningFunctions::normalizePidValue) + .filter(CleaningFunctions::pidFilter) + .collect(Collectors.toList()); + } + protected static StructuredProperty cleanValue(StructuredProperty s) { s.setValue(s.getValue().replaceAll(CLEANING_REGEX, " ")); return s; @@ -267,25 +280,23 @@ public class CleaningFunctions { /** * Utility method that filter PID values on a per-type basis. - * @param pid the PID whose value will be checked. - * @return true the PID containing the normalised value. + * @param s the PID whose value will be checked. + * @return false if the pid matches the filter criteria, true otherwise. */ - private static boolean filterPid(StructuredProperty pid) { - String value = Optional - .ofNullable(pid.getValue()) - .map(s -> StringUtils.replaceAll(s, "\\s", "")) - .orElse(""); - if (StringUtils.isBlank(value)) { + public static boolean pidFilter(StructuredProperty s) { + final String pidValue = s.getValue(); + if (Objects.isNull(s.getQualifier()) || + StringUtils.isBlank(pidValue) || + StringUtils.isBlank(pidValue.replaceAll("(?:\\n|\\r|\\t|\\s)", ""))) { return false; } - switch (pid.getQualifier().getClassid()) { - - // TODO add cleaning for more PID types as needed - case "doi": - return value.startsWith(DOI_PREFIX); - default: - return true; + if (CleaningFunctions.PID_BLACKLIST.contains(pidValue)) { + return false; } + if (PidBlacklistProvider.getBlacklist(s.getQualifier().getClassid()).contains(pidValue)) { + return false; + } + return true; } /** diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 0371a2879..398b842c8 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -106,7 +106,7 @@ public class IdentifierFactory implements Serializable { .getPid() .stream() .map(CleaningFunctions::normalizePidValue) - .filter(IdentifierFactory::pidFilter) + .filter(CleaningFunctions::pidFilter) .collect( Collectors .groupingBy( @@ -136,21 +136,25 @@ public class IdentifierFactory implements Serializable { // filter away PIDs provided by a DS that is not considered an authority for the // given PID Type .filter(p -> { - final PidType pType = PidType.tryValueOf(p.getQualifier().getClassid()); - return Optional.ofNullable(collectedFrom).isPresent() && - Optional - .ofNullable(PID_AUTHORITY.get(pType)) - .map(authorities -> { - return authorities.containsKey(collectedFrom.getKey()) - || authorities.containsValue(collectedFrom.getValue()); - }) - .orElse(false); + return shouldFilterPid(collectedFrom, p); }) .map(CleaningFunctions::normalizePidValue) - .filter(IdentifierFactory::pidFilter)) + .filter(CleaningFunctions::pidFilter)) .orElse(Stream.empty()); } + private static boolean shouldFilterPid(KeyValue collectedFrom, StructuredProperty p) { + final PidType pType = PidType.tryValueOf(p.getQualifier().getClassid()); + return pType.equals(PidType.handle) || Optional.ofNullable(collectedFrom).isPresent() && + Optional + .ofNullable(PID_AUTHORITY.get(pType)) + .map(authorities -> { + return authorities.containsKey(collectedFrom.getKey()) + || authorities.containsValue(collectedFrom.getValue()); + }) + .orElse(false); + } + /** * @see {@link IdentifierFactory#createIdentifier(OafEntity, boolean)} */ @@ -159,22 +163,6 @@ public class IdentifierFactory implements Serializable { return createIdentifier(entity, true); } - protected static boolean pidFilter(StructuredProperty s) { - final String pidValue = s.getValue(); - if (Objects.isNull(s.getQualifier()) || - StringUtils.isBlank(pidValue) || - StringUtils.isBlank(pidValue.replaceAll("(?:\\n|\\r|\\t|\\s)", ""))) { - return false; - } - if (CleaningFunctions.PID_BLACKLIST.contains(pidValue)) { - return false; - } - if (PidBlacklistProvider.getBlacklist(s.getQualifier().getClassid()).contains(pidValue)) { - return false; - } - return true; - } - private static String idFromPid(T entity, StructuredProperty s, boolean md5) { return new StringBuilder() .append(ModelSupport.getIdPrefix(entity.getClass())) From 3256b9c836542f90959630be1b6580598dde8827 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Fri, 19 Mar 2021 09:36:12 +0100 Subject: [PATCH 164/445] code formatting --- .../dhp/schema/oaf/CleaningFunctions.java | 29 ++++++++++--------- .../schema/oaf/utils/IdentifierFactory.java | 4 +-- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index 19b4c7618..1cee3058e 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -5,13 +5,13 @@ import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; -import com.google.common.collect.Sets; -import eu.dnetlib.dhp.schema.oaf.utils.PidBlacklistProvider; import org.apache.commons.lang3.StringUtils; import com.clearspring.analytics.util.Lists; +import com.google.common.collect.Sets; import eu.dnetlib.dhp.schema.common.ModelConstants; +import eu.dnetlib.dhp.schema.oaf.utils.PidBlacklistProvider; public class CleaningFunctions { @@ -148,16 +148,18 @@ public class CleaningFunctions { } if (Objects.nonNull(r.getInstance())) { - - for (Instance i : r.getInstance()) { final Set pids = Sets.newHashSet(i.getPid()); - i.setAlternateIdentifier( - Optional.ofNullable(i.getAlternateIdentifier()) - .map(altId -> altId.stream() - .filter(p -> !pids.contains(p)) - .collect(Collectors.toList())) - .orElse(Lists.newArrayList())); + i + .setAlternateIdentifier( + Optional + .ofNullable(i.getAlternateIdentifier()) + .map( + altId -> altId + .stream() + .filter(p -> !pids.contains(p)) + .collect(Collectors.toList())) + .orElse(Lists.newArrayList())); if (Objects.isNull(i.getAccessright()) || StringUtils.isBlank(i.getAccessright().getClassid())) { i @@ -236,7 +238,8 @@ public class CleaningFunctions { } private static List processPidCleaning(List pids) { - return pids.stream() + return pids + .stream() .filter(Objects::nonNull) .filter(sp -> StringUtils.isNotBlank(StringUtils.trim(sp.getValue()))) .filter(sp -> !PID_BLACKLIST.contains(sp.getValue().trim().toLowerCase())) @@ -286,8 +289,8 @@ public class CleaningFunctions { public static boolean pidFilter(StructuredProperty s) { final String pidValue = s.getValue(); if (Objects.isNull(s.getQualifier()) || - StringUtils.isBlank(pidValue) || - StringUtils.isBlank(pidValue.replaceAll("(?:\\n|\\r|\\t|\\s)", ""))) { + StringUtils.isBlank(pidValue) || + StringUtils.isBlank(pidValue.replaceAll("(?:\\n|\\r|\\t|\\s)", ""))) { return false; } if (CleaningFunctions.PID_BLACKLIST.contains(pidValue)) { diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java index 31f60636a..fe4642ee9 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/IdentifierFactory.java @@ -71,7 +71,7 @@ public class IdentifierFactory implements Serializable { .stream() .filter(Objects::nonNull) .filter(s -> s.getQualifier() != null && "doi".equalsIgnoreCase(s.getQualifier().getClassid())) - .filter(IdentifierFactory::pidFilter) + .filter(CleaningFunctions::pidFilter) .findAny() .orElse(null); } else { @@ -81,7 +81,7 @@ public class IdentifierFactory implements Serializable { .stream() .filter(i -> i.getPid() != null) .flatMap(i -> i.getPid().stream()) - .filter(IdentifierFactory::pidFilter) + .filter(CleaningFunctions::pidFilter) .findAny() .orElse(null); } From a4e82a65aacdc7aed07dc5eb9287a98ed8ac932d Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Fri, 19 Mar 2021 11:34:44 +0100 Subject: [PATCH 165/445] integrated filter applied when merging BETA & PROD graphs to rule our records from Datacite --- .../graph/merge/MergeGraphTableSparkJob.java | 112 ++++++++++-------- 1 file changed, 60 insertions(+), 52 deletions(-) diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/merge/MergeGraphTableSparkJob.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/merge/MergeGraphTableSparkJob.java index e53f4ca30..5f6550e10 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/merge/MergeGraphTableSparkJob.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/merge/MergeGraphTableSparkJob.java @@ -4,6 +4,7 @@ package eu.dnetlib.dhp.oa.graph.merge; import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; import java.util.*; +import java.util.stream.Collectors; import javax.xml.crypto.Data; @@ -52,22 +53,22 @@ public class MergeGraphTableSparkJob { public static void main(String[] args) throws Exception { String jsonConfiguration = IOUtils - .toString( - CleanGraphSparkJob.class - .getResourceAsStream( - "/eu/dnetlib/dhp/oa/graph/merge_graphs_parameters.json")); + .toString( + CleanGraphSparkJob.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/graph/merge_graphs_parameters.json")); final ArgumentApplicationParser parser = new ArgumentApplicationParser(jsonConfiguration); parser.parseArgument(args); String priority = Optional - .ofNullable(parser.get("priority")) - .orElse(PRIORITY_DEFAULT); + .ofNullable(parser.get("priority")) + .orElse(PRIORITY_DEFAULT); log.info("priority: {}", priority); Boolean isSparkSessionManaged = Optional - .ofNullable(parser.get("isSparkSessionManaged")) - .map(Boolean::valueOf) - .orElse(Boolean.TRUE); + .ofNullable(parser.get("isSparkSessionManaged")) + .map(Boolean::valueOf) + .orElse(Boolean.TRUE); log.info("isSparkSessionManaged: {}", isSparkSessionManaged); String betaInputPath = parser.get("betaInputPath"); @@ -89,48 +90,55 @@ public class MergeGraphTableSparkJob { conf.registerKryoClasses(ModelSupport.getOafModelClasses()); runWithSparkSession( - conf, - isSparkSessionManaged, - spark -> { - removeOutputDir(spark, outputPath); - mergeGraphTable(spark, priority, betaInputPath, prodInputPath, entityClazz, entityClazz, outputPath); - }); + conf, + isSparkSessionManaged, + spark -> { + removeOutputDir(spark, outputPath); + mergeGraphTable(spark, priority, betaInputPath, prodInputPath, entityClazz, entityClazz, outputPath); + }); } private static

void mergeGraphTable( - SparkSession spark, - String priority, - String betaInputPath, - String prodInputPath, - Class

p_clazz, - Class b_clazz, - String outputPath) { + SparkSession spark, + String priority, + String betaInputPath, + String prodInputPath, + Class

p_clazz, + Class b_clazz, + String outputPath) { Dataset> beta = readTableFromPath(spark, betaInputPath, b_clazz); Dataset> prod = readTableFromPath(spark, prodInputPath, p_clazz); prod - .joinWith(beta, prod.col("_1").equalTo(beta.col("_1")), "full_outer") - .map((MapFunction, Tuple2>, P>) value -> { - Optional

p = Optional.ofNullable(value._1()).map(Tuple2::_2); - Optional b = Optional.ofNullable(value._2()).map(Tuple2::_2); + .joinWith(beta, prod.col("_1").equalTo(beta.col("_1")), "full_outer") + .map((MapFunction, Tuple2>, P>) value -> { + Optional

p = Optional.ofNullable(value._1()).map(Tuple2::_2); + Optional b = Optional.ofNullable(value._2()).map(Tuple2::_2); - if (p.orElse((P) b.orElse((B) DATASOURCE)) instanceof Datasource) { - return mergeDatasource(p, b); - } - switch (priority) { - default: - case "BETA": - return mergeWithPriorityToBETA(p, b); - case "PROD": - return mergeWithPriorityToPROD(p, b); - } - }, Encoders.bean(p_clazz)) - .filter((FilterFunction

) Objects::nonNull) - .write() - .mode(SaveMode.Overwrite) - .option("compression", "gzip") - .json(outputPath); + if (p.orElse((P) b.orElse((B) DATASOURCE)) instanceof Datasource) { + return mergeDatasource(p, b); + } + switch (priority) { + default: + case "BETA": + return mergeWithPriorityToBETA(p, b); + case "PROD": + return mergeWithPriorityToPROD(p, b); + } + }, Encoders.bean(p_clazz)) + .filter((FilterFunction

) Objects::nonNull) + .filter((FilterFunction

) o -> { + HashSet collectedFromNames = Optional + .ofNullable(o.getCollectedfrom()) + .map(c -> c.stream().map(KeyValue::getValue).collect(Collectors.toCollection(HashSet::new))) + .orElse(new HashSet()); + return !collectedFromNames.contains("Datacite"); + }) + .write() + .mode(SaveMode.Overwrite) + .option("compression", "gzip") + .json(outputPath); } /** @@ -184,19 +192,19 @@ public class MergeGraphTableSparkJob { } private static Dataset> readTableFromPath( - SparkSession spark, String inputEntityPath, Class clazz) { + SparkSession spark, String inputEntityPath, Class clazz) { log.info("Reading Graph table from: {}", inputEntityPath); return spark - .read() - .textFile(inputEntityPath) - .map( - (MapFunction>) value -> { - final T t = OBJECT_MAPPER.readValue(value, clazz); - final String id = ModelSupport.idFn().apply(t); - return new Tuple2<>(id, t); - }, - Encoders.tuple(Encoders.STRING(), Encoders.kryo(clazz))); + .read() + .textFile(inputEntityPath) + .map( + (MapFunction>) value -> { + final T t = OBJECT_MAPPER.readValue(value, clazz); + final String id = ModelSupport.idFn().apply(t); + return new Tuple2<>(id, t); + }, + Encoders.tuple(Encoders.STRING(), Encoders.kryo(clazz))); } private static void removeOutputDir(SparkSession spark, String path) { From 5a043e95ea0c1011f451b4e662edfc05f3f27637 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Fri, 19 Mar 2021 11:37:27 +0100 Subject: [PATCH 166/445] code formatting --- .../graph/merge/MergeGraphTableSparkJob.java | 118 +++++++++--------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/merge/MergeGraphTableSparkJob.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/merge/MergeGraphTableSparkJob.java index 5f6550e10..68623dd55 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/merge/MergeGraphTableSparkJob.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/merge/MergeGraphTableSparkJob.java @@ -53,22 +53,22 @@ public class MergeGraphTableSparkJob { public static void main(String[] args) throws Exception { String jsonConfiguration = IOUtils - .toString( - CleanGraphSparkJob.class - .getResourceAsStream( - "/eu/dnetlib/dhp/oa/graph/merge_graphs_parameters.json")); + .toString( + CleanGraphSparkJob.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/graph/merge_graphs_parameters.json")); final ArgumentApplicationParser parser = new ArgumentApplicationParser(jsonConfiguration); parser.parseArgument(args); String priority = Optional - .ofNullable(parser.get("priority")) - .orElse(PRIORITY_DEFAULT); + .ofNullable(parser.get("priority")) + .orElse(PRIORITY_DEFAULT); log.info("priority: {}", priority); Boolean isSparkSessionManaged = Optional - .ofNullable(parser.get("isSparkSessionManaged")) - .map(Boolean::valueOf) - .orElse(Boolean.TRUE); + .ofNullable(parser.get("isSparkSessionManaged")) + .map(Boolean::valueOf) + .orElse(Boolean.TRUE); log.info("isSparkSessionManaged: {}", isSparkSessionManaged); String betaInputPath = parser.get("betaInputPath"); @@ -90,55 +90,55 @@ public class MergeGraphTableSparkJob { conf.registerKryoClasses(ModelSupport.getOafModelClasses()); runWithSparkSession( - conf, - isSparkSessionManaged, - spark -> { - removeOutputDir(spark, outputPath); - mergeGraphTable(spark, priority, betaInputPath, prodInputPath, entityClazz, entityClazz, outputPath); - }); + conf, + isSparkSessionManaged, + spark -> { + removeOutputDir(spark, outputPath); + mergeGraphTable(spark, priority, betaInputPath, prodInputPath, entityClazz, entityClazz, outputPath); + }); } private static

void mergeGraphTable( - SparkSession spark, - String priority, - String betaInputPath, - String prodInputPath, - Class

p_clazz, - Class b_clazz, - String outputPath) { + SparkSession spark, + String priority, + String betaInputPath, + String prodInputPath, + Class

p_clazz, + Class b_clazz, + String outputPath) { Dataset> beta = readTableFromPath(spark, betaInputPath, b_clazz); Dataset> prod = readTableFromPath(spark, prodInputPath, p_clazz); prod - .joinWith(beta, prod.col("_1").equalTo(beta.col("_1")), "full_outer") - .map((MapFunction, Tuple2>, P>) value -> { - Optional

p = Optional.ofNullable(value._1()).map(Tuple2::_2); - Optional b = Optional.ofNullable(value._2()).map(Tuple2::_2); + .joinWith(beta, prod.col("_1").equalTo(beta.col("_1")), "full_outer") + .map((MapFunction, Tuple2>, P>) value -> { + Optional

p = Optional.ofNullable(value._1()).map(Tuple2::_2); + Optional b = Optional.ofNullable(value._2()).map(Tuple2::_2); - if (p.orElse((P) b.orElse((B) DATASOURCE)) instanceof Datasource) { - return mergeDatasource(p, b); - } - switch (priority) { - default: - case "BETA": - return mergeWithPriorityToBETA(p, b); - case "PROD": - return mergeWithPriorityToPROD(p, b); - } - }, Encoders.bean(p_clazz)) - .filter((FilterFunction

) Objects::nonNull) - .filter((FilterFunction

) o -> { - HashSet collectedFromNames = Optional - .ofNullable(o.getCollectedfrom()) - .map(c -> c.stream().map(KeyValue::getValue).collect(Collectors.toCollection(HashSet::new))) - .orElse(new HashSet()); - return !collectedFromNames.contains("Datacite"); - }) - .write() - .mode(SaveMode.Overwrite) - .option("compression", "gzip") - .json(outputPath); + if (p.orElse((P) b.orElse((B) DATASOURCE)) instanceof Datasource) { + return mergeDatasource(p, b); + } + switch (priority) { + default: + case "BETA": + return mergeWithPriorityToBETA(p, b); + case "PROD": + return mergeWithPriorityToPROD(p, b); + } + }, Encoders.bean(p_clazz)) + .filter((FilterFunction

) Objects::nonNull) + .filter((FilterFunction

) o -> { + HashSet collectedFromNames = Optional + .ofNullable(o.getCollectedfrom()) + .map(c -> c.stream().map(KeyValue::getValue).collect(Collectors.toCollection(HashSet::new))) + .orElse(new HashSet()); + return !collectedFromNames.contains("Datacite"); + }) + .write() + .mode(SaveMode.Overwrite) + .option("compression", "gzip") + .json(outputPath); } /** @@ -192,19 +192,19 @@ public class MergeGraphTableSparkJob { } private static Dataset> readTableFromPath( - SparkSession spark, String inputEntityPath, Class clazz) { + SparkSession spark, String inputEntityPath, Class clazz) { log.info("Reading Graph table from: {}", inputEntityPath); return spark - .read() - .textFile(inputEntityPath) - .map( - (MapFunction>) value -> { - final T t = OBJECT_MAPPER.readValue(value, clazz); - final String id = ModelSupport.idFn().apply(t); - return new Tuple2<>(id, t); - }, - Encoders.tuple(Encoders.STRING(), Encoders.kryo(clazz))); + .read() + .textFile(inputEntityPath) + .map( + (MapFunction>) value -> { + final T t = OBJECT_MAPPER.readValue(value, clazz); + final String id = ModelSupport.idFn().apply(t); + return new Tuple2<>(id, t); + }, + Encoders.tuple(Encoders.STRING(), Encoders.kryo(clazz))); } private static void removeOutputDir(SparkSession spark, String path) { From 98854b01245ba086cb7aeaf7f6fc12656db7400d Mon Sep 17 00:00:00 2001 From: miconis Date: Fri, 19 Mar 2021 16:57:40 +0100 Subject: [PATCH 167/445] minor changes --- .../dhp/oa/dedup/SparkCopyOpenorgs.java | 11 +- .../oa/dedup/SparkCopyOpenorgsMergeRels.java | 18 +- .../dedup/SparkCopyRelationsNoOpenorgs.java | 18 +- .../dhp/oa/dedup/SparkRemoveDiffRels.java | 281 ------------------ .../oa/dedup/openorgs/oozie_app/workflow.xml | 14 +- .../dhp/oa/dedup/scan/oozie_app/workflow.xml | 21 +- .../dhp/oa/dedup/SparkOpenorgsTest.java | 77 +---- .../openorgs/organization/organization.gz | Bin 0 -> 71922 bytes .../dhp/dedup/openorgs/relation/relation.gz | Bin 0 -> 22489 bytes 9 files changed, 56 insertions(+), 384 deletions(-) delete mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkRemoveDiffRels.java create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/organization/organization.gz create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/relation/relation.gz diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java index ff7aca627..7984f0104 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java @@ -68,12 +68,9 @@ public class SparkCopyOpenorgs extends AbstractSparkAction { log.info("Copying openorgs to the working dir"); final String outputPath = DedupUtility.createDedupRecordPath(workingPath, actionSetId, subEntity); - removeOutputDir(spark, outputPath); final String entityPath = DedupUtility.createEntityPath(graphBasePath, subEntity); - final Class clazz = ModelSupport.entityTypes.get(EntityType.valueOf(subEntity)); - filterOpenorgs(spark, entityPath) .write() .mode(SaveMode.Overwrite) @@ -95,9 +92,13 @@ public class SparkCopyOpenorgs extends AbstractSparkAction { .rdd(), Encoders.bean(Organization.class)); - entities.show(); + log.info("Number of organization entities processed: {}", entities.count()); - return entities.filter(entities.col("id").contains("openorgs____")); + entities = entities.filter(entities.col("id").contains("openorgs____")); + + log.info("Number of Openorgs organization entities: {}", entities.count()); + + return entities; } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java index 4bb46222e..201043a08 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java @@ -74,8 +74,6 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { final String outputPath = DedupUtility.createMergeRelPath(workingPath, actionSetId, "organization"); - removeOutputDir(spark, outputPath); - final String relationPath = DedupUtility.createEntityPath(graphBasePath, "relation"); DedupConfig dedupConf = getConfigurations(isLookUpService, actionSetId).get(0); @@ -85,11 +83,13 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { .textFile(relationPath) .map(patchRelFn(), Encoders.bean(Relation.class)) .toJavaRDD() - .filter(this::isOpenorgs) // takes only relations coming from openorgs - .filter(this::filterOpenorgsRels) // takes only isSimilarTo relations between organizations from openorgs - .filter(this::excludeOpenorgsMesh) // excludes relations between an organization and an openorgsmesh + .filter(this::isOpenorgs) + .filter(this::filterOpenorgsRels) + .filter(this::excludeOpenorgsMesh) .filter(this::excludeNonOpenorgs); // excludes relations with no openorgs id involved + log.info("Number of raw Openorgs Relations collected: {}", rawRels.count()); + // turn openorgs isSimilarTo relations into mergerels JavaRDD mergeRelsRDD = rawRels.flatMap(rel -> { List mergerels = new ArrayList<>(); @@ -103,6 +103,8 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { return mergerels.iterator(); }); + log.info("Number of Openorgs Merge Relations created: {}", mergeRelsRDD.count()); + spark .createDataset( mergeRelsRDD.rdd(), @@ -134,7 +136,7 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { if (rel.getCollectedfrom() != null) { for (KeyValue k : rel.getCollectedfrom()) { - if (k.getValue().equals("OpenOrgs Database")) { + if (k.getValue() != null && k.getValue().equals("OpenOrgs Database")) { return true; } } @@ -144,7 +146,7 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { private boolean excludeOpenorgsMesh(Relation rel) { - if (rel.getSource().equals("openorgsmesh") || rel.getTarget().equals("openorgsmesh")) { + if (rel.getSource().contains("openorgsmesh") || rel.getTarget().contains("openorgsmesh")) { return false; } return true; @@ -152,7 +154,7 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { private boolean excludeNonOpenorgs(Relation rel) { - if (rel.getSource().equals("openorgs____") || rel.getTarget().equals("openorgs____")) { + if (rel.getSource().contains("openorgs____") || rel.getTarget().contains("openorgs____")) { return true; } return false; diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRelationsNoOpenorgs.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRelationsNoOpenorgs.java index 64a110892..71bab79d0 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRelationsNoOpenorgs.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRelationsNoOpenorgs.java @@ -33,7 +33,7 @@ import scala.Tuple2; public class SparkCopyRelationsNoOpenorgs extends AbstractSparkAction { - private static final Logger log = LoggerFactory.getLogger(SparkUpdateEntity.class); + private static final Logger log = LoggerFactory.getLogger(SparkCopyRelationsNoOpenorgs.class); public SparkCopyRelationsNoOpenorgs(ArgumentApplicationParser parser, SparkSession spark) { super(parser, spark); @@ -52,7 +52,7 @@ public class SparkCopyRelationsNoOpenorgs extends AbstractSparkAction { conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer"); conf.registerKryoClasses(ModelSupport.getOafModelClasses()); - new SparkUpdateEntity(parser, getSparkSession(conf)) + new SparkCopyRelationsNoOpenorgs(parser, getSparkSession(conf)) .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); } @@ -69,14 +69,14 @@ public class SparkCopyRelationsNoOpenorgs extends AbstractSparkAction { final String relationPath = DedupUtility.createEntityPath(graphBasePath, "relation"); final String outputPath = DedupUtility.createEntityPath(dedupGraphPath, "relation"); - removeOutputDir(spark, outputPath); - JavaRDD simRels = spark .read() .textFile(relationPath) .map(patchRelFn(), Encoders.bean(Relation.class)) .toJavaRDD() - .filter(this::excludeOpenorgsRels); + .filter(x -> !isOpenorgs(x)); + + log.info("Number of non-Openorgs relations collected: {}", simRels.count()); spark .createDataset(simRels.rdd(), Encoders.bean(Relation.class)) @@ -96,15 +96,15 @@ public class SparkCopyRelationsNoOpenorgs extends AbstractSparkAction { }; } - private boolean excludeOpenorgsRels(Relation rel) { + private boolean isOpenorgs(Relation rel) { if (rel.getCollectedfrom() != null) { for (KeyValue k : rel.getCollectedfrom()) { - if (k.getValue().equals("OpenOrgs Database")) { - return false; + if (k.getValue() != null && k.getValue().equals("OpenOrgs Database")) { + return true; } } } - return true; + return false; } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkRemoveDiffRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkRemoveDiffRels.java deleted file mode 100644 index 4c0bfadf0..000000000 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkRemoveDiffRels.java +++ /dev/null @@ -1,281 +0,0 @@ - -package eu.dnetlib.dhp.oa.dedup; - -import static eu.dnetlib.dhp.oa.dedup.SparkCreateMergeRels.DNET_PROVENANCE_ACTIONS; -import static eu.dnetlib.dhp.oa.dedup.SparkCreateMergeRels.PROVENANCE_ACTION_CLASS; -import static eu.dnetlib.dhp.oa.dedup.SparkCreateMergeRels.hash; - -import java.io.IOException; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -import org.apache.commons.io.IOUtils; -import org.apache.spark.SparkConf; -import org.apache.spark.api.java.JavaPairRDD; -import org.apache.spark.api.java.JavaRDD; -import org.apache.spark.api.java.JavaSparkContext; -import org.apache.spark.api.java.function.MapFunction; -import org.apache.spark.api.java.function.PairFunction; -import org.apache.spark.graphx.Edge; -import org.apache.spark.rdd.RDD; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Encoders; -import org.apache.spark.sql.SaveMode; -import org.apache.spark.sql.SparkSession; -import org.dom4j.DocumentException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.Iterables; - -import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.oa.dedup.graph.ConnectedComponent; -import eu.dnetlib.dhp.oa.dedup.graph.GraphProcessor; -import eu.dnetlib.dhp.oa.dedup.model.Block; -import eu.dnetlib.dhp.schema.oaf.DataInfo; -import eu.dnetlib.dhp.schema.oaf.Qualifier; -import eu.dnetlib.dhp.schema.oaf.Relation; -import eu.dnetlib.dhp.utils.ISLookupClientFactory; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import eu.dnetlib.pace.config.DedupConfig; -import eu.dnetlib.pace.model.MapDocument; -import eu.dnetlib.pace.util.MapDocumentUtil; -import scala.Tuple2; - -public class SparkRemoveDiffRels extends AbstractSparkAction { - - private static final Logger log = LoggerFactory.getLogger(SparkRemoveDiffRels.class); - - public SparkRemoveDiffRels(ArgumentApplicationParser parser, SparkSession spark) { - super(parser, spark); - } - - public static void main(String[] args) throws Exception { - ArgumentApplicationParser parser = new ArgumentApplicationParser( - IOUtils - .toString( - SparkCreateSimRels.class - .getResourceAsStream( - "/eu/dnetlib/dhp/oa/dedup/createSimRels_parameters.json"))); - parser.parseArgument(args); - - SparkConf conf = new SparkConf(); - new SparkCreateSimRels(parser, getSparkSession(conf)) - .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); - } - - @Override - public void run(ISLookUpService isLookUpService) - throws DocumentException, IOException, ISLookUpException { - - // read oozie parameters - final String graphBasePath = parser.get("graphBasePath"); - final String isLookUpUrl = parser.get("isLookUpUrl"); - final String actionSetId = parser.get("actionSetId"); - final String workingPath = parser.get("workingPath"); - final int numPartitions = Optional - .ofNullable(parser.get("numPartitions")) - .map(Integer::valueOf) - .orElse(NUM_PARTITIONS); - - log.info("numPartitions: '{}'", numPartitions); - log.info("graphBasePath: '{}'", graphBasePath); - log.info("isLookUpUrl: '{}'", isLookUpUrl); - log.info("actionSetId: '{}'", actionSetId); - log.info("workingPath: '{}'", workingPath); - - // for each dedup configuration - for (DedupConfig dedupConf : getConfigurations(isLookUpService, actionSetId)) { - - final String entity = dedupConf.getWf().getEntityType(); - final String subEntity = dedupConf.getWf().getSubEntityValue(); - log.info("Removing diffrels for: '{}'", subEntity); - - final String mergeRelsPath = DedupUtility.createMergeRelPath(workingPath, actionSetId, subEntity); - - final String relationPath = DedupUtility.createEntityPath(graphBasePath, subEntity); - - final String openorgsMergeRelsPath = DedupUtility - .createOpenorgsMergeRelsPath(workingPath, actionSetId, subEntity); - - final int maxIterations = dedupConf.getWf().getMaxIterations(); - log.info("Max iterations {}", maxIterations); - - JavaRDD mergeRelsRDD = spark - .read() - .load(mergeRelsPath) - .as(Encoders.bean(Relation.class)) - .where("relClass == 'merges'") - .toJavaRDD(); - - System.out.println("mergeRelsRDD = " + mergeRelsRDD.count()); - -// JavaRDD, String>> diffRelsRDD = spark -// .read() -// .textFile(relationPath) -// .map(patchRelFn(), Encoders.bean(Relation.class)) -// .toJavaRDD() -// .filter(r -> filterRels(r, entity)) -// .map(rel -> { -// if (rel.getSource().compareTo(rel.getTarget()) < 0) -// return new Tuple2<>(new Tuple2<>(rel.getSource(), rel.getTarget()), "diffRel"); -// else -// return new Tuple2<>(new Tuple2<>(rel.getTarget(), rel.getSource()), "diffRel"); -// }); - // THIS IS FOR TESTING PURPOSE - JavaRDD, String>> diffRelsRDD = spark - .read() - .load(mergeRelsPath) - .as(Encoders.bean(Relation.class)) - .toJavaRDD() - .map(rel -> { - if (rel.getSource().compareTo(rel.getTarget()) < 0) - return new Tuple2<>(new Tuple2<>(rel.getSource(), rel.getTarget()), "diffRel"); - else - return new Tuple2<>(new Tuple2<>(rel.getTarget(), rel.getSource()), "diffRel"); - }) - .distinct(); - - System.out.println("diffRelsRDD = " + diffRelsRDD.count()); - -// JavaRDD, String>> flatMergeRels = mergeRelsRDD -// .mapToPair(rel -> new Tuple2<>(rel.getSource(), rel.getTarget())) -// .groupByKey() -// .flatMap(g -> { -// List, String>> rels = new ArrayList<>(); -// -// List ids = StreamSupport -// .stream(g._2().spliterator(), false) -// .collect(Collectors.toList()); -// -// for (int i = 0; i < ids.size(); i++) { -// for (int j = i + 1; j < ids.size(); j++) { -// if (ids.get(i).compareTo(ids.get(j)) < 0) -// rels.add(new Tuple2<>(new Tuple2<>(ids.get(i), ids.get(j)), g._1())); -// else -// rels.add(new Tuple2<>(new Tuple2<>(ids.get(j), ids.get(i)), g._1())); -// } -// } -// return rels.iterator(); -// -// }); - JavaRDD, String>> mergeRels = mergeRelsRDD - .map(rel -> { - if (rel.getSource().compareTo(rel.getTarget()) < 0) - return new Tuple2<>(new Tuple2<>(rel.getSource(), rel.getTarget()), "mergeRel"); - else - return new Tuple2<>(new Tuple2<>(rel.getTarget(), rel.getSource()), "mergeRel"); - }); - System.out.println("mergeRelsProcessed = " + mergeRels.count()); - -// JavaRDD purgedMergeRels = flatMergeRels -// .union(diffRelsRDD) -// .mapToPair(rel -> new Tuple2<>(rel._1(), Arrays.asList(rel._2()))) -// .reduceByKey((a, b) -> { -// List list = new ArrayList(); -// list.addAll(a); -// list.addAll(b); -// return list; -// }) -// .filter(rel -> rel._2().size() == 1) -// .mapToPair(rel -> new Tuple2<>(rel._2().get(0), rel._1())) -// .flatMap(rel -> { -// List> rels = new ArrayList<>(); -// String source = rel._1(); -// rels.add(new Tuple2<>(source, rel._2()._1())); -// rels.add(new Tuple2<>(source, rel._2()._2())); -// return rels.iterator(); -// }) -// .distinct() -// .flatMap(rel -> tupleToMergeRel(rel, dedupConf)); - JavaRDD purgedMergeRels = mergeRels - .union(diffRelsRDD) - .mapToPair(t -> new Tuple2<>(t._1()._1() + "|||" + t._1()._2(), t._2())) - .groupByKey() - .filter(g -> Iterables.size(g._2()) == 1) - .flatMap( - t -> tupleToMergeRel( - new Tuple2<>(t._1().split("\\|\\|\\|")[0], t._1().split("\\|\\|\\|")[1]), - dedupConf)); - - System.out.println("purgedMergeRels = " + purgedMergeRels.count()); - - spark - .createDataset(purgedMergeRels.rdd(), Encoders.bean(Relation.class)) - .write() - .mode(SaveMode.Overwrite) - .json(openorgsMergeRelsPath); - } - } - - private static MapFunction patchRelFn() { - return value -> { - final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); - if (rel.getDataInfo() == null) { - rel.setDataInfo(new DataInfo()); - } - return rel; - }; - } - - private boolean filterRels(Relation rel, String entityType) { - - switch (entityType) { - case "result": - if (rel.getRelClass().equals("isDifferentFrom") && rel.getRelType().equals("resultResult") - && rel.getSubRelType().equals("dedup")) - return true; - break; - case "organization": - if (rel.getRelClass().equals("isDifferentFrom") && rel.getRelType().equals("organizationOrganization") - && rel.getSubRelType().equals("dedup")) - return true; - break; - default: - return false; - } - return false; - } - - public Iterator tupleToMergeRel(Tuple2 rel, DedupConfig dedupConf) { - - List rels = new ArrayList<>(); - - rels.add(rel(rel._1(), rel._2(), "merges", dedupConf)); - rels.add(rel(rel._2(), rel._1(), "isMergedIn", dedupConf)); - - return rels.iterator(); - } - - private Relation rel(String source, String target, String relClass, DedupConfig dedupConf) { - - String entityType = dedupConf.getWf().getEntityType(); - - Relation r = new Relation(); - r.setSource(source); - r.setTarget(target); - r.setRelClass(relClass); - r.setRelType(entityType + entityType.substring(0, 1).toUpperCase() + entityType.substring(1)); - r.setSubRelType("dedup"); - - DataInfo info = new DataInfo(); - info.setDeletedbyinference(false); - info.setInferred(true); - info.setInvisible(false); - info.setInferenceprovenance(dedupConf.getWf().getConfigurationId()); - Qualifier provenanceAction = new Qualifier(); - provenanceAction.setClassid(PROVENANCE_ACTION_CLASS); - provenanceAction.setClassname(PROVENANCE_ACTION_CLASS); - provenanceAction.setSchemeid(DNET_PROVENANCE_ACTIONS); - provenanceAction.setSchemename(DNET_PROVENANCE_ACTIONS); - info.setProvenanceaction(provenanceAction); - - // TODO calculate the trust value based on the similarity score of the elements in the CC - // info.setTrust(); - - r.setDataInfo(info); - return r; - } -} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml index 339e99084..dc63d0a79 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml @@ -87,8 +87,8 @@ - - + + @@ -113,7 +113,7 @@ --graphBasePath${graphBasePath} --isLookUpUrl${isLookUpUrl} - --actionSetId${actionSetId} + --actionSetId${actionSetIdOpenorgs} --workingPath${workingPath} --numPartitions8000 @@ -142,7 +142,7 @@ --graphBasePath${graphBasePath} --isLookUpUrl${isLookUpUrl} --workingPath${workingPath} - --actionSetId${actionSetId} + --actionSetId${actionSetIdOpenorgs} --numPartitions8000 @@ -169,7 +169,7 @@ --graphBasePath${graphBasePath} --workingPath${workingPath} --isLookUpUrl${isLookUpUrl} - --actionSetId${actionSetId} + --actionSetId${actionSetIdOpenorgs} --cutConnectedComponent${cutConnectedComponent} @@ -196,7 +196,7 @@ --graphBasePath${graphBasePath} --workingPath${workingPath} --isLookUpUrl${isLookUpUrl} - --actionSetId${actionSetId} + --actionSetId${actionSetIdOpenorgs} --dbUrl${dbUrl} --dbTable${dbTable} --dbUser${dbUser} @@ -227,7 +227,7 @@ --graphBasePath${graphBasePath} --workingPath${workingPath} --isLookUpUrl${isLookUpUrl} - --actionSetId${actionSetId} + --actionSetId${actionSetIdOpenorgs} --apiUrl${apiUrl} --dbUrl${dbUrl} --dbTable${dbTable} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml index 998f3ac21..c28a2a921 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml @@ -92,11 +92,23 @@ + + + + + + + + + + + + yarn @@ -182,7 +194,7 @@ yarn cluster - Copy Merge Relations + Copy Openorgs Merge Relations eu.dnetlib.dhp.oa.dedup.SparkCopyOpenorgsMergeRels dhp-dedup-openaire-${projectVersion}.jar @@ -201,7 +213,7 @@ --actionSetId${actionSetIdOpenorgs} --numPartitions8000 - + @@ -210,7 +222,7 @@ yarn cluster - Copy Entities + Copy Openorgs Entities eu.dnetlib.dhp.oa.dedup.SparkCopyOpenorgs dhp-dedup-openaire-${projectVersion}.jar @@ -225,7 +237,6 @@ --graphBasePath${graphBasePath} --workingPath${workingPath} - --isLookUpUrl${isLookUpUrl} --actionSetId${actionSetIdOpenorgs} @@ -262,7 +273,7 @@ yarn cluster - Update Entity + Copy Non-Openorgs Relations eu.dnetlib.dhp.oa.dedup.SparkCopyRelationsNoOpenorgs dhp-dedup-openaire-${projectVersion}.jar diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java index f8627d023..6ad2145a9 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java @@ -19,9 +19,11 @@ import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; import org.apache.spark.api.java.function.FilterFunction; +import org.apache.spark.api.java.function.ForeachFunction; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; @@ -61,7 +63,7 @@ public class SparkOpenorgsTest implements Serializable { public static void cleanUp() throws IOException, URISyntaxException { testGraphBasePath = Paths - .get(SparkOpenorgsTest.class.getResource("/eu/dnetlib/dhp/dedup/entities").toURI()) + .get(SparkOpenorgsTest.class.getResource("/eu/dnetlib/dhp/dedup/openorgs").toURI()) .toFile() .getAbsolutePath(); testOutputBasePath = createTempDirectory(SparkOpenorgsTest.class.getSimpleName() + "-") @@ -71,9 +73,8 @@ public class SparkOpenorgsTest implements Serializable { .toAbsolutePath() .toString(); -// FileUtils.deleteDirectory(new File(testOutputBasePath)); + FileUtils.deleteDirectory(new File(testOutputBasePath)); FileUtils.deleteDirectory(new File(testDedupGraphBasePath)); - FileUtils.deleteDirectory(new File("/tmp/test-orchestrator/organization_openorgs_mergerels")); final SparkConf conf = new SparkConf(); conf.set("spark.sql.shuffle.partitions", "200"); @@ -133,7 +134,7 @@ public class SparkOpenorgsTest implements Serializable { .textFile(testOutputBasePath + "/" + testActionSetId + "/organization_deduprecord") .count(); - assertEquals(0, orgs_deduprecord); + assertEquals(100, orgs_deduprecord); } @Test @@ -161,7 +162,7 @@ public class SparkOpenorgsTest implements Serializable { .load(testOutputBasePath + "/" + testActionSetId + "/organization_mergerel") .count(); - assertEquals(0, orgs_mergerel); + assertEquals(6, orgs_mergerel); } @@ -190,67 +191,7 @@ public class SparkOpenorgsTest implements Serializable { .textFile(testOutputBasePath + "/" + testActionSetId + "/organization_simrel") .count(); - System.out.println("orgs_simrel = " + orgs_simrel); - } - - @Test - public void createSimRelsTest() throws Exception { - - ArgumentApplicationParser parser = new ArgumentApplicationParser( - IOUtils - .toString( - SparkCreateSimRels.class - .getResourceAsStream( - "/eu/dnetlib/dhp/oa/dedup/createSimRels_parameters.json"))); - parser - .parseArgument( - new String[] { - "-i", testGraphBasePath, - "-asi", testActionSetId, - "-la", "lookupurl", - "-w", "/tmp", - "-np", "50" - }); - - new SparkCreateSimRels(parser, spark).run(isLookUpService); - - long orgs_simrel = spark - .read() - .textFile("/tmp/" + testActionSetId + "/organization_simrel") - .count(); - - assertEquals(3082, orgs_simrel); - } - - @Test - public void createMergeRelsTest() throws Exception { - - ArgumentApplicationParser parser = new ArgumentApplicationParser( - IOUtils - .toString( - SparkCreateMergeRels.class - .getResourceAsStream( - "/eu/dnetlib/dhp/oa/dedup/createCC_parameters.json"))); - parser - .parseArgument( - new String[] { - "-i", - testGraphBasePath, - "-asi", - testActionSetId, - "-la", - "lookupurl", - "-w", - "/tmp" - }); - - new SparkCreateMergeRels(parser, spark).run(isLookUpService); - - long orgs_mergerel = spark - .read() - .load("/tmp/" + testActionSetId + "/organization_mergerel") - .count(); - assertEquals(1272, orgs_mergerel); + assertEquals(96, orgs_simrel); } @Test @@ -273,9 +214,7 @@ public class SparkOpenorgsTest implements Serializable { long relations = jsc.textFile(testDedupGraphBasePath + "/relation").count(); -// Dataset relsRDD = spark.read().textFile(testDedupGraphBasePath + "/relation").map(patchRelFn(), Encoders.bean(Relation.class)); - - assertEquals(500, relations); + assertEquals(400, relations); } @AfterAll diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/organization/organization.gz b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/organization/organization.gz new file mode 100644 index 0000000000000000000000000000000000000000..45b0edeb252685bace9873a5810da3a49169093e GIT binary patch literal 71922 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

$p)H|Z>W9Cnu4MbUWh=|rt|f1y=2 z+EM-DgFIAlMs{c-eoZxyHt@U_YT;y4FkS%917bLr~?xa zT3ayCL90?Gi4K|=4Oooh^WBR>QV8slZvY-7-SxYf2x{^&AXW|}=zVZ+Y5Sm#la zn3CGu{_f$q-ziS*oZNCw-^BFw@thp=0Zf=Ue!`o#{J2Y6Wlda^q-I4N8X8K)?#>KK zv~^cUq3+P5sd6%C?Q@whT=#vx;2%pzMXxLs7rJD<)G0k@6XT>?uNf|3K;f=h-O(6@rr>dBFj!&swQD^0x8!md<|~2fx@X> zfApC&rb2=QipJB;yeH#0l;KB>CGu$7Az*m{Sdq5{f!^v#*#v;*D8}{x)t*#AbaYEs zov=`IY3drZnX~6>wcNZk88oo=?}Dh8-Zl!9Q_Og9Kcx$vV=) zhZym!_0KMQZ!nsVwvD26hd91;gyP`*(=DJd-2xR65$G1c@(FZ{M>(>3{kbc^>P`1l z+3FoH|L;QW*%MaH4W@faoAd5lhnG}Qnf5PvU<*vk()M71>B0Gi{^7a4>fQS0{s%Zl z(jz_*6)jQcERr_lm^$sO>@E(0t)HMmMG_bc+X|VI5**~T(+(M?Eu|$c_#Wv_Tn)@H zzGY6IpUrugv@>5*S2ZLXnC)zzc>7#4GVH)&^RvcVL?mp7aR0zd6OP)*Kq?bHrA z83z)l0xV;CCY#B_`+t`CJ2*ILnqIeZ81aJc;&0mO2;;{n^fdpoBTrs!i!0Kb6tmie z7h-z!dtN}^-HS0DD8de}sk_;cc;ru%Pb zdq;u_^go&%b62TdScnhg_Qg!|Ryy95Qq8@n0E?&U+|K5tJJ z=NC+N-p0~gzH5qkPjc9Ek~=BU0k%eAqzc#+1tn#^rq6WYZKUS*ow9&cL!ali*>a=& zNSLhc#596Wr6h%+V;3FV%~_<-c1)`cgT3Cb;*F7>s`QYv0%vT$4|X?q3tz3;N3hRV z^DQR@@2*up3y}pyPj(wwj6kIDt1N`eUb%h1rBx@r?w3<_Y-8wnt7aDD`fakTxKK`DBKqqY7l{k!USW~!I zWSGZ$;X*{DSslGlo8-DS(J8+4x-V{BS)F^uVnWbbGcw1;v_5QOORhL)J26pXdF9D%kPfZV5Hje3K%=wi3J4w*V{UlR{*It zleHAkp-8dcz5-;Mr=#+YUhqrWVZ7Q)lUQV-Smxk1^I?(=vTXgVpSfEGs!jBQF(4j% znjR*}qTHw7l+-f4{FS=JT*|kXvQ*>gHOB2bNtv2BFmqn?*A_C zHB#kdH#)0@7UtzqU#1SrCa9r{KJUI5tffMzZYLuFY>wM|@Kv~%(fl0tQg1+VR!0xD zxT17zpE7IU?daPC9eS^YEUU@Py9YdD1urT18LR!8B!E+bzCL;ak+#25R}~`VCq8Q% z_tyq)uNS%MDa1GDPD@dWR|CstDL%^Fc_XP8dE$XZJum0yF^UoRqfj0$i2E%&#x#<9 zloC<($><*?dXw)pgYLKt`(qBA@JA5$e{lQ=hJXbiVA&aE8SV&l@r;IIl-AqH}^L{mFkS7iG z8v0y52%(q9&dEz><6caLREqI}o=W^T9ox6M1`TF6Us&A1ZVP$+IYM>7nfP-ek(>K( zvxgF%)VT#EnCU9tRg#lE$f~NlENn=?GWx$Z_yx9~m5VY8YlDJczPaWgsZ8G+qkbPN71f7<$7!usX59l00L zs`pQ%+t_*;VdX2GAXQ#F-mPH1^oRXVCjH%7ZwjV{mW|M#4BGL07+#)_7GE8DqTsgj zaQyEFCy)mv?HNLGh*kAGR|)9fs%z&2cfStw$*6PX=9!ALoB}JrZrH=id!-t4&r-*q z<9hw>FX!gj$E~)ozwl!JjBIYFoYUrE*pt&Xv&3}ThrBi9lvqkuOY3G=ac^9ClICUi z=siK?LV{Y*i^5D@+NjZHVM6ATovp<;cBmyLg+*4^FD@l@;I(<@SzW8;11H+zjmo0q zNc`HOXTQ(u94sJh+bn;3N|O*HN!`fN>}X1&y?M3*E(um|A#IHv0PPIdpkx0dd&}8E``G};&Lp2fwkz#q6Y7N%SN-oIoH785g1JJ zl8x(h48~6e`>(?mT?0HK6mCIbUc@d%xuQ z>D?kaZoS?9c(Kr4MqIj3F=biCk^A^9w*0-rUuGkpZEj%eRsRdD#>vwmHttHZe=CuM z$NN*`Ud}@n`wzMc41(}of_$~-?+%o3hkeAr1yFwSyfj?1eFdv1O6t{(r=NA|#cWn8BzH#T^a zW_;W6i2e#Kuf4s~jMLUyDe|)miP^sSb8RmnK^YGFd!cYCM!h=jdPdOk=T0zn`0V0* z<|SHzFHX=}cY%9xd@wU=lKMX2fz-!}RQm2%=HAS>;X%!VnPJOoQfsXPxawxTSqsv* zgBgOxsr5z{xrBUrKdB+7yuiIz66I-T^JIQ{ly265&|X&Hx7GpC?s!zp|J+NHirCEA zO?j<=ayG)hS;r>m!@Yzqx%$+)*cA7fWWns}pc_GhGnavv?t+%_)#m9H@%6F-U$ckH z-*n$oAhyR&<8hk2b^)^;-~xjbf|kGq(rU$jmui;0)kMB}6SP07t$;;5D4_B;GjDRU zG^-j3N+V8_!lVcgw;a@%tF7>?0H0*X!8WB>Rm(zTbFZ(U* zdkCzI#kZWi$rOUNEX(%@V(}?F-D&{5<;tf`w?=`8ukYq2cco*oM$q|}JbbI= zlK+qbfym5KLu7)7yWzvbqM&Av$b960snBRJN|#s{Bh^uCFiM*@+QKSVj>iR-`XWJb z4_XPXrO!K zB0JP0n8*u5kQBOH59CzLw}%bIL-8T~SMwdt_s)un6-egnQg=Jp^9Z^Dzj%Wu@N)CI8pIKw{U6=g z%b({VyXAh5fb(MmIX$91>jJ4eSiq51WPRe>jt-CA`W{{3NjTAWf& zuSo&CO$=ysg0W zvhGqu3oV8kT&=K!ejsKCPCl^I&&TbA-hM3f;>yxKwaqk|V5!_->tm!IMbJYxbI9~t zd72E_)#>+Mw(KMC$O$D7+Yj8|7?Bg_xOU5fQQ(%Fd07dNTgp9O)l04nx;yxcr-O^x zs?;x1&+>k$e8LTlqfdHTpx8DHz6SYaM!TReyCU@q?~9-If5*07zYrF2yIh_i>{{Wg z&};DsBcYJIuZ4iFj>rm><$s1yYBCIydGM*ApNv|1&58(-l@%BSRbjOdpsQCiJp2+0 z%dAHZxZxHL!VYTi)VQcw`$zHMbA#7Z(mr{{nl9G!Xh(AeBODt*`Cvt|B)nuOxuFR$jMLp zfi&l&hMeCw)t4iN;$_2zsNDaJ9;J!*LIf3Bx#RI7m{hTC<;dlVDs>*R)q&ZHEC@xw zRMCjxcF2ag*W`P?@H(3xHG-BYYbL4apAC!=Av89pmTz~qP1I9+^5qIb__uA+?>GG} z&Dsdc3QnJ;4!U%Avx$={2z;#aDv!A*Y3)f|Rvf#e#E7H#(0|-!4sx5utPRL*9G|bI zYv*?0WS5M-&0GFlHOj(N$Q2UG$mV9V*$5483G*GyLGD^s5x~2UExmALO1*1C97>vU zZ{Aw+UsKV0YA8oR6u+~|D!N98e62ncx@Qk0Lqej8mx=$rrDH2$F1RDEim$+PEt1|q zBB>}Se+Gyo)t#sm3-BJOfNVi*Tk$o4&swMu*+<2_k0ikIS}5`!4NQnf+~k?-J988wd=^Sl&dCP zf~INpodeWiR!>4g^+9OLP{@x$mmlS(JH?$i%3*>vvqzi<3u88+9k@Mr8QfYs$|rrv zRrd}tiJsmgp0P&ML!HaO_p_kDvBO(43o_do>)<&XqbjEw96>U*nZkhm@dQPSo|>g} zBv}08I+SOJZ9pgwMGCsKq%*S4n2x;(P3Z}3YcntQbPGT{&j7v1{3lhHJL;d!C-Lj~ z6g=be7xa&IY`kzE@#DR6T0G)7SeW-UnoD?V-pbNg95&gMYhu!x$7-Q`*O!1w(C+$p zFhQ9>{5HX(25>wCb5A}jQW3r-&MPL%>xT7(kNCQn2*kg-@c#9~QdL_jn|-l;G~+0# z=)U3t$A-uKm)*ra2DH&h@(L40ZH8$+uL9!8hbmrJ`tI->1->e}QKR)IeTM4}-so9f z?nI>f;V90{+rA~-KdEmK+*H>c^#5xtt4J@EQ~gAMJf=SO(uI?DtmMHaIrhB+tE-m_ zVncC`B7KFzAF5p0$ui;wyS^hz2DMEk+o+2Yd=L|<-D&}@@q@v+FO!3M>%s1ApB=X zs(Fm05}Us~qH{4}I*!;Ih<)4rb=1Io)52Z7vZkB|Th2cZ)YU6%*tNA5$A2pMDMiR^ zC-L-~Z8tF0ah5Q$;v?0}#)UN~Sj(DH3s5Nsy@*9v7=;~Bcx{*o(?n&sT#{Ync{*{~ zJg0ypzSU5}mz%0BmUW{_B(Lj4f;w1-vcC#yEMxO(=RbhL+y!13vIL3Q!;14WGFHQf;4CktWH8@^^Nu-ARxoP4-S2LNM!%R|d zEv}~29|P-3UZ>ht77gtw3jf-U8SpfthIT?3o3xDX(VNev+4r5{yot+=d%S^UMFHUR zpJ7e6E&XeODW}(V1E}teW?Y4)o=teB^F0W#mB^n0m%60vAL#nkHfNBN_N0v z>d&uLY%qFup}<_R(cy`s^2g@7mUuDPGS7WhpP1_+Yf2N5bla>sgL&X}1b6hL637yG z??_Y7mJJrVN5Q4FoCPunhB$`G?SdGB@Jg)+#jh^Z=*i6b1eaVt!4jho+0m{vn0W&N zge?B?StUEV9sNy{WTkj&z@(l1O;#oY({U0%z5@us|Bi#>SeWmJK$kQVM{|X2fm>O zBv&K@f}Q%x_bR-EjIlTNj%{jMd z1x3hUej*8PvDM3n9hqA~*kxhF58yx9LrOrvD<;3OZrMSi=x7a=5KHR-~OUMs@Qf^-= zFS0MMgnNB%(K~5A-j~n+fZqSIuSy{xqYx7SrvMw7n!-D74* zeTri*u1-`hOX1%jHU~WEqjo}!&8kSE#uHvmuK zwYN0GD>3nS(Vq6=v3CDz6;FMG(5Z_Ni^Im{iV3coWBbX}%JGj_Wp|yo9v%%&n-eDi zjBI4I1eo{l*F&ym?}tU{6e7t;@{Ew(lT0|8HW;*REy{TmnN- z-$&C;M>(=Ty~8f|Y3?CO5qB4@G!<<*mV7`tw>@FlZ@B^J|HcG>^+!qydjoOmFD>&lKi6?8I1G3TN#Q$1s9t8BgWrp1|%24O~rjEx`{<0BVdi^+Ox|g)fzKv zvdbxTAq{U4%D{XmMDca4|KMa!)^=l9VAKL=&YUz+;;yk9lptbWlTIU%l}y($h-o$? zz6}5cvVsz3A+Q3%K102J)gygfDuG6=y@CB@8-v;Y?Ap3ln@`?5>5frozpM&!1!cQ1 zReHL}Nef$R`!=%lnw(0w#`S(#!G1dcT!a0xmZ6aAE~cZqL_%z0!>n@L+xLY|Xoc&k z%sjr1LTmt#99&YyW+7r>!+q~~J-U>)KG#akcusyixqfrIrMKkvqbHpcgy>gG!RYKp z{K}xeSQ@kK!ykNWqiXB9ByDln*fpLd?!^QqzGb>wiLD{7KHGe=)VYl73x&ZBC_t7v zlTW^JDw2Mu@{aZ6*H)v{pWl+qWFJyuSo+io=KQ`fiD5ah=24q?H7=jFLBpXW%I@j- zPbNR*RYdP!57*`v*YlTibE{`p3fQQl`nnJ0Qj*80lq28rSzHSF7p#+iXxb*~+N)1W z{6}h}uvhvHmS(V#6c?fG;J@LU(!g9r+u1ezbVz5n{q4T$HQl1Napb@5zT!59xBs5IeqH*YN1^R@ z?mM9y*J1NGyvPu%Ryf)XFd=z2VMNS?* zG*}-x&95LKXdZ}eVPr*M=DwwHCWJVIedE$eFnREFr6Wp5?@>nhr2`M`A9i-B>dT3R zUKvLtZ&-3nPGo&rw7EAt{ZX2(PI&$<4<7fwYr|LE?X-S3+O6c^6hppbMda(aBXOp) z*)g?fxrx)JCT_IL^AF-M{q3p>iS<}Ecuj2*BAz#)To$6Z9?=S|m2 zAC7q(HvIfWxj;q3MndS|yxwP3YsF!*i$>Tl2+g3IT2HD?qgPJ$j{chEx_YdZyN~L7 zw%|W6dna^r`H1V_(c{4$RtGqfX1YSiplO~_UkF_ujM#@?lnNq=$K`+|mND!BXg)3< zBT6urrh_}imBu-971IW?u=7e8>zLqw#QX-`7}4#I*DT@0|74?cdUg5NvJ?DP^1ELc zXy~YiS?SBOi3ORNh2;crZ26C2roUsjM7cA`q00=Hg3F+NOu=P_OHl|C=kX?iQ$v+T z=@h0A$9_{;xjiun|9uf((|8be$v4m->@0*%ZTOQSOvN2wsWDpya4N!N!XR!KCBb|+ z4U_eZUeThJ(|cZ8@22Fz!Am^tP9py@2++Q3sfqC1Gkq3s8hD0it(#7K=%GfEa2ZgR z#qwQ?F1%yXz!KbqRT-)--<3%Dls0drRN&SaTw~2RD~xH7%D=2*65=Fn|1v-j1&+ep z=Ia>7!*e-DtBT8{MZ!zm9LXR2Qd4AR5BWN$pMRvlCV2(8@aiHvKk-iB$&tQT&fP&~ zO9=v<=NxenRPJSBK@~YDmG&@lPBl%OvAA+v{x$SD zbIxzE@y8bbA2Fn;|BUOKP2KBXh^nDY*clnI{{C(4UZc=b&w}ZpRw8pZ?lWRk@vEhV#m`DV{l47i z@qyqJB5MGr#&2@HTa$axXt!#M3b|eW{=Ob8EAqIYdswm%|7F5;lizXZ7}p%ax|^}9m)i=qXgq03QxiV(Lm9zkU ziJE5u@J^5Du3pl1m~T<4SVtefNZmNG8{O#aypi>eR#-Sj6mj+!c48S|8k)s)XXQu3>+m#TBista8_PKbtXOetMO^IxhaP zJ=SX?XnUfF$KZk@`|ld}w%0u3Wlo3ZwoW2bJYN4!u>WnFOt_a^>Y&3LdFnMk&k!?1 z`R9+vz%d4I7&ited$wU#5}TKi=-BnQzz29}1WiMSV=T&U`QU%Kh7kiOh2q7S1;Ayn z4Xc#V!eJ4?>T7u`mcPkA^_8E+lYQpKcML0tQ7UhI2d+Ivd{@M1LaL)_}$(fMtI?bw;<8yt34$2@p zAsa&Rg|3xUv%b}@_Y3CD)>>+1Iip!szb?69@A5lgMV91pxljo~Z++@Eq2JTT2r1>9O=(uDOv5JRW#vYK2UBabic1DzxE z`706=U+_7kcThei6Km(@OPZIO1=4y^n$KApn*W9#b`mjMjmaNg5}p`3Q`q4f;Wa?H zn~!CSfP)-NlfH^0j#7`TUPX#NF}LI9l#eBpihrpEskjbiIWYQ{TJ{w!WyNiS*sP~a z>&}a6K@5+u?C={P)Lx{p1E?Ljfofhu2hRqp3R`oxxEqgCQwW>Be|>sP~Imy#(2sVJufI}&&ixx;x3p&O?V&S5d zAgpj_N?xYLX-;5CfRvS3wJKCO`DIc7_#A={pkM%eaI(|9mqb~@E?gaJkKC}(>d8H4 zE}^Yh1@GSYjzQ4OufS47JS9oD4Sygrq9imCN6AvSzX-Rx*0cOT&-#vif%GhY87dWt zJ2q~T68cVS`(Ze;zp?kJ`V(#}Ma%*SnNzV90WzC=!!YCCwP5r^aGW_{nISM$9=HBw zKx#-fx(K0Ks93ZZa?=OqgvAJ4LzF!87hwE2XYzih`}q$!JbCA27)nj&Hhw)#NH>dM z!NKTo|%=*#apJ{aL$?o-=(PMK1cqGPq_H(fvom|Z3 z^mxYK&x2Na&0KY96GEfJ_A_a6ZVtVR z6A_l}a6J2a|J+h2A&yh5I4*R3QZfgBLkLEKQ>`PhOXWfUd7IEwZTA<)#YW(5LQ^K` zEFch7q_xb!=cPb@48stb&H`5+T|?%n^)L3)!}$KL+jG~d<^7($m}ooS%P~pg6Tcb@ z#KW*Av-JT@FVEWuqhxaSMy77pZaVy!QG`^zj{4>L+P8NjG42Jc@nh?Z+Qez#>Ph3B6xlDQ3IrGx4 zl=S^`;TNDQ=nP$N9~(L8O4Mn2e!J+MvF|wWhBRq3KbwxyC|9{>|L4`1fQbkeB%U;W z?lpX=0Qh>$69wrmm2Fs&a*^hn0d8yOzfA(nC<&4*BSZM`EfvfnNdM(xs{lVcb8d70 zLz)zazBtD=4vNNvLB#NwTP#Oe{_L@J)(($I=&_`0zrY7P(vw?BSu9bSY~j8Gq7eD_ zmZFT2IiusWf08>nw;EUArlc)KA}$qj7)ibZkm+EUNx*bK+00#`1!GOjE7*x%=xX^y zjhX1R4@m(xdW~Lfa*e{^31jhr6eVV%On%JZoDv857^}>m()ccmGLzN&nb=C#(zXzo z6eOgOv@LWdA5T3H*)k5{%X01HL%*RDL{%N3OS?vHf&3E+BJd@!x!0MJ>A zd%r#R8E4nbm||Dfm`&PGNEz{;;lx4ED{4Of(XeJ5RvE{JMlYqTrAzg^m4D+iDtcW7 z&4j9;fb0SEX4bH)QiS?`ifSc6d(gu49K| zcLabgr&arMd+lrv+@A(@8Q1POTnX~> z7^Y7a=+-ZFX2Vq=_QX(`fWm}Yl67>_4+}Q_Q`w~sYQs%1(Uf(h>NX7dYH4n0nxrz6 zoCBx80TtbAL$L!xfjTc_DE4hwLZ4v@`6pK8Zuy*n{;Gj6WQW$+oX^hS7ZXVMCitR& zIv|}*Gl984#VD^Do$}H1zjr?6;**Cls5-nVXJU46~{5-@J_tNysQi7}KQ7?l&?8n(|TxYb)q@Q$b zzM_RVAWGqO$0sYY`MI5AJyInKB)M){roAm)e=OIEN&ps$w@W*udmO+p` zCMN(I?-z-hvS~|oak&BYVsQi9a$?Fh{?&aL0~n@|IKZd|9?FTC2s{*{n%!Ts>eI}9 z4@Ncq2YEOu8g#<3lCBlczTg=^yY?af0s^C=JLgYO>=uHTmO

h+M&p)GUe{?{P0w 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 literal 0 HcmV?d00001 From 0fe40b08e4e92e8c0e4c3ff3f79c2a7f97bd3386 Mon Sep 17 00:00:00 2001 From: miconis Date: Fri, 19 Mar 2021 17:12:05 +0100 Subject: [PATCH 168/445] addition of deduplication profiles for the results, double check on pids and the title with a lower threshold --- .../profiles/dataset_dedup_configuration.xml | 386 +++++++++++++++++ ...herresearchproduct_dedup_configuration.xml | 387 +++++++++++++++++ .../publication_dedup_configuration.xml | 399 ++++++++++++++++++ .../result_deduplication_orchestrator.xml | 27 ++ .../profiles/software_dedup_configuration.xml | 128 ++++++ 5 files changed, 1327 insertions(+) create mode 100644 dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/dataset_dedup_configuration.xml create mode 100644 dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/otherresearchproduct_dedup_configuration.xml create mode 100644 dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/publication_dedup_configuration.xml create mode 100644 dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/result_deduplication_orchestrator.xml create mode 100644 dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/software_dedup_configuration.xml diff --git a/dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/dataset_dedup_configuration.xml b/dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/dataset_dedup_configuration.xml new file mode 100644 index 000000000..ead28e19b --- /dev/null +++ b/dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/dataset_dedup_configuration.xml @@ -0,0 +1,386 @@ + +

+ + + + + +
+ + + Dataset: Decision Tree Dedup - v2.0 + + { + "wf" : { + "threshold" : "0.99", + "dedupRun" : "001", + "entityType" : "result", + "subEntityType" : "resulttype", + "subEntityValue" : "dataset", + "orderField" : "title", + "queueMaxSize" : "200", + "groupMaxSize" : "100", + "maxChildren" : "100", + "slidingWindowSize" : "50", + "rootBuilder" : ["result", "resultProject_outcome_isProducedBy", "resultResult_publicationDataset_isRelatedTo", "resultResult_similarity_isAmongTopNSimilarDocuments", "resultResult_similarity_hasAmongTopNSimilarDocuments", "resultOrganization_affiliation_hasAuthorInstitution", "resultResult_part_hasPart", "resultResult_part_isPartOf", "resultResult_supplement_isSupplementTo", "resultResult_supplement_isSupplementedBy", "resultResult_version_isVersionOf" ], + "includeChildren" : "true", + "idPath" : "$.id", + "maxIterations" : 20 + }, + "pace" : { + "clustering" : [ + { "name" : "wordsStatsSuffixPrefixChain", "fields" : [ "title" ], "params" : { "mod" : "10" } }, + { "name" : "lowercase", "fields" : [ "doi" ], "params" : { } } + ], + "decisionTree" : { + "start" : { + "fields": [ + { + "field": "pid", + "comparator": "jsonListMatch", + "weight": 1.0, + "countIfUndefined": "false", + "params": { + "jpath_value": "$.value", + "jpath_classid": "$.qualifier.classid" + } + } + ], + "threshold": 0.5, + "aggregation": "AVG", + "positive": "layer1", + "negative": "layer2", + "undefined": "layer2", + "ignoreUndefined": "true" + }, + "layer1": { + "fields": [ + { + "field": "title", + "comparator": "levensteinTitle", + "weight": 1.0, + "countIfUndefined": "true", + "params": {} + } + ], + "threshold": 0.9, + "aggregation": "AVG", + "positive": "MATCH", + "negative": "NO_MATCH", + "undefined": "NO_MATCH", + "ignoreUndefined": "true" + }, + "layer2" : { + "fields": [ + { + "field": "title", + "comparator": "titleVersionMatch", + "weight": 1.0, + "countIfUndefined": "false", + "params": {} + }, + { + "field": "authors", + "comparator": "sizeMatch", + "weight": 1.0, + "countIfUndefined": "false", + "params": {} + } + ], + "threshold": 1.0, + "aggregation": "AND", + "positive": "layer3", + "negative": "NO_MATCH", + "undefined": "layer3", + "ignoreUndefined": "false" + }, + "layer3" : { + "fields": [ + { + "field": "title", + "comparator": "levensteinTitle", + "weight": 1.0, + "countIfUndefined": "true", + "params": {} + } + ], + "threshold": 0.99, + "aggregation": "AVG", + "positive": "MATCH", + "negative": "NO_MATCH", + "undefined": "NO_MATCH", + "ignoreUndefined": "true" + } + }, + "model" : [ + { + "name" : "doi", + "type" : "String", + "path" : "$.pid[?(@.qualifier.classid == 'doi')].value" + }, + { + "name" : "pid", + "type" : "JSON", + "path" : "$.pid", + "overrideMatch" : "true" + }, + { + "name" : "title", + "type" : "String", + "path" : "$.title[?(@.qualifier.classid == 'main title')].value", + "length" : 250, + "size" : 5 + }, + { + "name" : "authors", + "type" : "String", + "path" : "$.author[*].fullname", + "size" : 200 + }, + { + "name" : "resulttype", + "type" : "String", + "path" : "$.resulttype.classid" + } + ], + "blacklists": { + "title": [ + "(?i)^Data Management Plan", + "^Inside Front Cover$", + "(?i)^Poster presentations$", + "^THE ASSOCIATION AND THE GENERAL MEDICAL COUNCIL$", + "^Problems with perinatal pathology\\.?$", + "(?i)^Cases? of Puerperal Convulsions$", + "(?i)^Operative Gyna?ecology$", + "(?i)^Mind the gap\\!?\\:?$", + "^Chronic fatigue syndrome\\.?$", + "^Cartas? ao editor Letters? to the Editor$", + "^Note from the Editor$", + "^Anesthesia Abstract$", + "^Annual report$", + "(?i)^“?THE RADICAL PREVENTION OF VENEREAL DISEASE\\.?”?$", + "(?i)^Graph and Table of Infectious Diseases?$", + "^Presentation$", + "(?i)^Reviews and Information on Publications$", + "(?i)^PUBLIC HEALTH SERVICES?$", + "(?i)^COMBINED TEXT-?BOOK OF OBSTETRICS AND GYN(Æ|ae)COLOGY$", + "(?i)^Adrese autora$", + "(?i)^Systematic Part .*\\. Catalogus Fossilium Austriae, Band 2: Echinoidea neogenica$", + "(?i)^Acknowledgement to Referees$", + "(?i)^Behçet's disease\\.?$", + "(?i)^Isolation and identification of restriction endonuclease.*$", + "(?i)^CEREBROVASCULAR DISEASES?.?$", + "(?i)^Screening for abdominal aortic aneurysms?\\.?$", + "^Event management$", + "(?i)^Breakfast and Crohn's disease.*\\.?$", + "^Cálculo de concentraciones en disoluciones acuosas. Ejercicio interactivo\\..*\\.$", + "(?i)^Genetic and functional analyses of SHANK2 mutations suggest a multiple hit model of Autism spectrum disorders?\\.?$", + "^Gushi hakubutsugaku$", + "^Starobosanski nadpisi u Bosni i Hercegovini \\(.*\\)$", + "^Intestinal spirocha?etosis$", + "^Treatment of Rodent Ulcer$", + "(?i)^\\W*Cloud Computing\\W*$", + "^Compendio mathematico : en que se contienen todas las materias mas principales de las Ciencias que tratan de la cantidad$", + "^Free Communications, Poster Presentations: Session [A-F]$", + "^“The Historical Aspects? of Quackery\\.?”$", + "^A designated centre for people with disabilities operated by St John of God Community Services (Limited|Ltd), Louth$", + "^P(er|re)-Mile Premiums for Auto Insurance\\.?$", + "(?i)^Case Report$", + "^Boletín Informativo$", + "(?i)^Glioblastoma Multiforme$", + "(?i)^Nuevos táxones animales descritos en la península Ibérica y Macaronesia desde 1994 \\(.*\\)$", + "^Zaměstnanecké výhody$", + "(?i)^The Economics of Terrorism and Counter-Terrorism: A Survey \\(Part .*\\)$", + "(?i)^Carotid body tumours?\\.?$", + "(?i)^\\[Españoles en Francia : La condición Emigrante.*\\]$", + "^Avant-propos$", + "(?i)^St\\. Patrick's Cathedral, Dublin, County Dublin - Head(s)? and Capital(s)?$", + "(?i)^St\\. Patrick's Cathedral, Dublin, County Dublin - Bases?$", + "(?i)^PUBLIC HEALTH VERSUS THE STATE$", + "^Viñetas de Cortázar$", + "(?i)^Search for heavy neutrinos and W(\\[|_|\\(|_\\{|-)?R(\\]|\\)|\\})? bosons with right-handed couplings in a left-right symmetric model in pp collisions at.*TeV(\\.)?$", + "(?i)^Measurement of the pseudorapidity and centrality dependence of the transverse energy density in Pb(-?)Pb collisions at.*tev(\\.?)$", + "(?i)^Search for resonances decaying into top-quark pairs using fully hadronic decays in pp collisions with ATLAS at.*TeV$", + "(?i)^Search for neutral minimal supersymmetric standard model Higgs bosons decaying to tau pairs in pp collisions at.*tev$", + "(?i)^Relatório de Estágio (de|em) Angiologia e Cirurgia Vascular$", + "^Aus der AGMB$", + "^Znanstveno-stručni prilozi$", + "(?i)^Zhodnocení finanční situace podniku a návrhy na zlepšení$", + "(?i)^Evaluation of the Financial Situation in the Firm and Proposals to its Improvement$", + "(?i)^Hodnocení finanční situace podniku a návrhy na její zlepšení$", + "^Finanční analýza podniku$", + "^Financial analysis( of business)?$", + "(?i)^Textbook of Gyn(a)?(Æ)?(e)?cology$", + "^Jikken nihon shūshinsho$", + "(?i)^CORONER('|s)(s|') INQUESTS$", + "(?i)^(Μελέτη παραγόντων )?risk management( για ανάπτυξη και εφαρμογή ενός πληροφοριακού συστήματος| και ανάπτυξη συστήματος)?$", + "(?i)^Consultants' contract(s)?$", + "(?i)^Upute autorima$", + "(?i)^Bijdrage tot de Kennis van den Godsdienst der Dajaks van Lan(d|f)ak en Tajan$", + "^Joshi shin kokubun$", + "^Kōtō shōgaku dokuhon nōson'yō$", + "^Jinjō shōgaku shōka$", + "^Shōgaku shūjichō$", + "^Nihon joshi dokuhon$", + "^Joshi shin dokuhon$", + "^Chūtō kanbun dokuhon$", + "^Wabun dokuhon$", + "(?i)^(Analysis of economy selected village or town|Rozbor hospodaření vybrané obce či města)$", + "(?i)^cardiac rehabilitation$", + "(?i)^Analytical summary$", + "^Thesaurus resolutionum Sacrae Congregationis Concilii$", + "(?i)^Sumario analítico(\\s{1})?(Analitic summary)?$", + "^Prikazi i osvrti$", + "^Rodinný dům s provozovnou$", + "^Family house with an establishment$", + "^Shinsei chūtō shin kokugun$", + "^Pulmonary alveolar proteinosis(\\.?)$", + "^Shinshū kanbun$", + "^Viñeta(s?) de Rodríguez$", + "(?i)^RUBRIKA UREDNIKA$", + "^A Matching Model of the Academic Publication Market$", + "^Yōgaku kōyō$", + "^Internetový marketing$", + "^Internet marketing$", + "^Chūtō kokugo dokuhon$", + "^Kokugo dokuhon$", + "^Antibiotic Cover for Dental Extraction(s?)$", + "^Strategie podniku$", + "^Strategy of an Enterprise$", + "(?i)^respiratory disease(s?)(\\.?)$", + "^Award(s?) for Gallantry in Civil Defence$", + "^Podniková kultura$", + "^Corporate Culture$", + "^Severe hyponatraemia in hospital inpatient(s?)(\\.?)$", + "^Pracovní motivace$", + "^Work Motivation$", + "^Kaitei kōtō jogaku dokuhon$", + "^Konsolidovaná účetní závěrka$", + "^Consolidated Financial Statements$", + "(?i)^intracranial tumour(s?)$", + "^Climate Change Mitigation Options and Directed Technical Change: A Decentralized Equilibrium Analysis$", + "^\\[CERVECERIAS MAHOU(\\.|\\:) INTERIOR\\] \\[Material gráfico\\]$", + "^Housing Market Dynamics(\\:|\\.) On the Contribution of Income Shocks and Credit Constraint(s?)$", + "^\\[Funciones auxiliares de la música en Radio París,.*\\]$", + "^Úroveň motivačního procesu jako způsobu vedení lidí$", + "^The level of motivation process as a leadership$", + "^Pay-beds in N(\\.?)H(\\.?)S(\\.?) Hospitals$", + "(?i)^news and events$", + "(?i)^NOVOSTI I DOGAĐAJI$", + "^Sansū no gakushū$", + "^Posouzení informačního systému firmy a návrh změn$", + "^Information System Assessment and Proposal for ICT Modification$", + "^Stresové zatížení pracovníků ve vybrané profesi$", + "^Stress load in a specific job$", + "^Sunday: Poster Sessions, Pt.*$", + "^Monday: Poster Sessions, Pt.*$", + "^Wednesday: Poster Sessions, Pt.*", + "^Tuesday: Poster Sessions, Pt.*$", + "^Analýza reklamy$", + "^Analysis of advertising$", + "^Shōgaku shūshinsho$", + "^Shōgaku sansū$", + "^Shintei joshi kokubun$", + "^Taishō joshi kokubun dokuhon$", + "^Joshi kokubun$", + "^Účetní uzávěrka a účetní závěrka v ČR$", + "(?i)^The \"?Causes\"? of Cancer$", + "^Normas para la publicación de artículos$", + "^Editor('|s)(s|') [Rr]eply$", + "^Editor(’|s)(s|’) letter$", + "^Redaktoriaus žodis$", + "^DISCUSSION ON THE PRECEDING PAPER$", + "^Kōtō shōgaku shūshinsho jidōyō$", + "^Shōgaku nihon rekishi$", + "^(Theory of the flow of action currents in isolated myelinated nerve fibers).*$", + "^Préface$", + "^Occupational [Hh]ealth [Ss]ervices.$", + "^In Memoriam Professor Toshiyuki TAKESHIMA$", + "^Účetní závěrka ve vybraném podniku.*$", + "^Financial statements in selected company$", + "^Abdominal [Aa]ortic [Aa]neurysms.*$", + "^Pseudomyxoma peritonei$", + "^Kazalo autora$", + "(?i)^uvodna riječ$", + "^Motivace jako způsob vedení lidí$", + "^Motivation as a leadership$", + "^Polyfunkční dům$", + "^Multi\\-funkcional building$", + "^Podnikatelský plán$", + "(?i)^Podnikatelský záměr$", + "(?i)^Business Plan$", + "^Oceňování nemovitostí$", + "^Marketingová komunikace$", + "^Marketing communication$", + "^Sumario Analítico$", + "^Riječ uredništva$", + "^Savjetovanja i priredbe$", + "^Índice$", + "^(Starobosanski nadpisi).*$", + "^Vzdělávání pracovníků v organizaci$", + "^Staff training in organization$", + "^(Life Histories of North American Geometridae).*$", + "^Strategická analýza podniku$", + "^Strategic Analysis of an Enterprise$", + "^Sadržaj$", + "^Upute suradnicima$", + "^Rodinný dům$", + "(?i)^Fami(l)?ly house$", + "^Upute autorima$", + "^Strategic Analysis$", + "^Finanční analýza vybraného podniku$", + "^Finanční analýza$", + "^Riječ urednika$", + "(?i)^Content(s?)$", + "(?i)^Inhalt$", + "^Jinjō shōgaku shūshinsho jidōyō$", + "(?i)^Index$", + "^Chūgaku kokubun kyōkasho$", + "^Retrato de una mujer$", + "^Retrato de un hombre$", + "^Kōtō shōgaku dokuhon$", + "^Shotōka kokugo$", + "^Shōgaku dokuhon$", + "^Jinjō shōgaku kokugo dokuhon$", + "^Shinsei kokugo dokuhon$", + "^Teikoku dokuhon$", + "^Instructions to Authors$", + "^KİTAP TAHLİLİ$", + "^PRZEGLĄD PIŚMIENNICTWA$", + "(?i)^Presentación$", + "^İçindekiler$", + "(?i)^Tabl?e of contents$", + "^(CODICE DEL BEATO DE LOS REYES FERNANDO I Y SANCHA).*$", + "^(\\[MADRID\\. BIBL\\. NAC\\. N.*KING FERDINAND I.*FROM SAN ISIDORO DE LEON\\. FACUNDUS SCRIPSIT DATED.*\\]).*", + "^Editorial( Board)?$", + "(?i)^Editorial \\(English\\)$", + "^Editörden$", + "^(Corpus Oral Dialectal \\(COD\\)\\.).*$", + "^(Kiri Karl Morgensternile).*$", + "^(\\[Eksliibris Aleksandr).*\\]$", + "^(\\[Eksliibris Aleksandr).*$", + "^(Eksliibris Aleksandr).*$", + "^(Kiri A\\. de Vignolles).*$", + "^(2 kirja Karl Morgensternile).*$", + "^(Pirita kloostri idaosa arheoloogilised).*$", + "^(Kiri tundmatule).*$", + "^(Kiri Jenaer Allgemeine Literaturzeitung toimetusele).*$", + "^(Eksliibris Nikolai Birukovile).*$", + "^(Eksliibris Nikolai Issakovile).*$", + "^(WHP Cruise Summary Information of section).*$", + "^(Measurement of the top quark\\-pair production cross section with ATLAS in pp collisions at).*$", + "^(Measurement of the spin\\-dependent structure function).*", + "(?i)^.*authors['’′]? reply\\.?$", + "(?i)^.*authors['’′]? response\\.?$" + ] + }, + "synonyms" : {} + } + } + + + + + SECURITY_PARAMETERS + + \ No newline at end of file diff --git a/dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/otherresearchproduct_dedup_configuration.xml b/dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/otherresearchproduct_dedup_configuration.xml new file mode 100644 index 000000000..015e5abb6 --- /dev/null +++ b/dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/otherresearchproduct_dedup_configuration.xml @@ -0,0 +1,387 @@ + +
+ + + + + +
+ + + Other research product: Decision Tree Dedup - v2.0 + + { + "wf" : { + "threshold" : "0.99", + "dedupRun" : "001", + "entityType" : "result", + "subEntityType" : "resulttype", + "subEntityValue" : "otherresearchproduct", + "orderField" : "title", + "queueMaxSize" : "200", + "groupMaxSize" : "100", + "maxChildren" : "100", + "slidingWindowSize" : "50", + "rootBuilder" : [ "result", "resultProject_outcome_isProducedBy", "resultResult_publicationDataset_isRelatedTo", "resultResult_similarity_isAmongTopNSimilarDocuments", "resultResult_similarity_hasAmongTopNSimilarDocuments", "resultOrganization_affiliation_hasAuthorInstitution", "resultResult_part_hasPart", "resultResult_part_isPartOf", "resultResult_supplement_isSupplementTo", "resultResult_supplement_isSupplementedBy", "resultResult_version_isVersionOf" ], + "includeChildren" : "true", + "idPath" : "$.id", + "maxIterations" : 20 + }, + "pace" : { + "clustering" : [ + { "name" : "wordsStatsSuffixPrefixChain", "fields" : [ "title" ], "params" : { "mod" : "10" } }, + { "name" : "lowercase", "fields" : [ "doi" ], "params" : { } } + ], + "decisionTree" : { + "start" : { + "fields": [ + { + "field": "pid", + "comparator": "jsonListMatch", + "weight": 1.0, + "countIfUndefined": "false", + "params": { + "jpath_value": "$.value", + "jpath_classid": "$.qualifier.classid" + } + } + ], + "threshold": 0.5, + "aggregation": "AVG", + "positive": "layer1", + "negative": "layer2", + "undefined": "layer2", + "ignoreUndefined": "true" + }, + "layer1": { + "fields": [ + { + "field": "title", + "comparator": "levensteinTitle", + "weight": 1.0, + "countIfUndefined": "true", + "params": {} + } + ], + "threshold": 0.9, + "aggregation": "AVG", + "positive": "MATCH", + "negative": "NO_MATCH", + "undefined": "NO_MATCH", + "ignoreUndefined": "true" + }, + "layer2" : { + "fields": [ + { + "field": "title", + "comparator": "titleVersionMatch", + "weight": 1.0, + "countIfUndefined": "false", + "params": {} + }, + { + "field": "authors", + "comparator": "sizeMatch", + "weight": 1.0, + "countIfUndefined": "false", + "params": {} + } + ], + "threshold": 1.0, + "aggregation": "AND", + "positive": "layer3", + "negative": "NO_MATCH", + "undefined": "layer3", + "ignoreUndefined": "false" + }, + "layer3" : { + "fields": [ + { + "field": "title", + "comparator": "levensteinTitle", + "weight": 1.0, + "countIfUndefined": "true", + "params": {} + } + ], + "threshold": 0.99, + "aggregation": "AVG", + "positive": "MATCH", + "negative": "NO_MATCH", + "undefined": "NO_MATCH", + "ignoreUndefined": "true" + } + }, + "model" : [ + { + "name" : "doi", + "type" : "String", + "path" : "$.pid[?(@.qualifier.classid == 'doi')].value" + }, + { + "name" : "pid", + "type" : "JSON", + "path" : "$.pid", + "overrideMatch" : "true" + }, + { + "name" : "title", + "type" : "String", + "path" : "$.title[?(@.qualifier.classid == 'main title')].value", + "length" : 250, + "size" : 5 + }, + { + "name" : "authors", + "type" : "String", + "path" : "$.author[*].fullname", + "size" : 200 + }, + { + "name" : "resulttype", + "type" : "String", + "path" : "$.resulttype.classid" + } + ], + "blacklists": { + "title": [ + "(?i)^Data Management Plan", + "^Inside Front Cover$", + "(?i)^Poster presentations$", + "^THE ASSOCIATION AND THE GENERAL MEDICAL COUNCIL$", + "^Problems with perinatal pathology\\.?$", + "(?i)^Cases? of Puerperal Convulsions$", + "(?i)^Operative Gyna?ecology$", + "(?i)^Mind the gap\\!?\\:?$", + "^Chronic fatigue syndrome\\.?$", + "^Cartas? ao editor Letters? to the Editor$", + "^Note from the Editor$", + "^Anesthesia Abstract$", + "^Annual report$", + "(?i)^“?THE RADICAL PREVENTION OF VENEREAL DISEASE\\.?”?$", + "(?i)^Graph and Table of Infectious Diseases?$", + "^Presentation$", + "(?i)^Reviews and Information on Publications$", + "(?i)^PUBLIC HEALTH SERVICES?$", + "(?i)^COMBINED TEXT-?BOOK OF OBSTETRICS AND GYN(Æ|ae)COLOGY$", + "(?i)^Adrese autora$", + "(?i)^Systematic Part .*\\. Catalogus Fossilium Austriae, Band 2: Echinoidea neogenica$", + "(?i)^Acknowledgement to Referees$", + "(?i)^Behçet's disease\\.?$", + "(?i)^Isolation and identification of restriction endonuclease.*$", + "(?i)^CEREBROVASCULAR DISEASES?.?$", + "(?i)^Screening for abdominal aortic aneurysms?\\.?$", + "^Event management$", + "(?i)^Breakfast and Crohn's disease.*\\.?$", + "^Cálculo de concentraciones en disoluciones acuosas. Ejercicio interactivo\\..*\\.$", + "(?i)^Genetic and functional analyses of SHANK2 mutations suggest a multiple hit model of Autism spectrum disorders?\\.?$", + "^Gushi hakubutsugaku$", + "^Starobosanski nadpisi u Bosni i Hercegovini \\(.*\\)$", + "^Intestinal spirocha?etosis$", + "^Treatment of Rodent Ulcer$", + "(?i)^\\W*Cloud Computing\\W*$", + "^Compendio mathematico : en que se contienen todas las materias mas principales de las Ciencias que tratan de la cantidad$", + "^Free Communications, Poster Presentations: Session [A-F]$", + "^“The Historical Aspects? of Quackery\\.?”$", + "^A designated centre for people with disabilities operated by St John of God Community Services (Limited|Ltd), Louth$", + "^P(er|re)-Mile Premiums for Auto Insurance\\.?$", + "(?i)^Case Report$", + "^Boletín Informativo$", + "(?i)^Glioblastoma Multiforme$", + "(?i)^Nuevos táxones animales descritos en la península Ibérica y Macaronesia desde 1994 \\(.*\\)$", + "^Zaměstnanecké výhody$", + "(?i)^The Economics of Terrorism and Counter-Terrorism: A Survey \\(Part .*\\)$", + "(?i)^Carotid body tumours?\\.?$", + "(?i)^\\[Españoles en Francia : La condición Emigrante.*\\]$", + "^Avant-propos$", + "(?i)^St\\. Patrick's Cathedral, Dublin, County Dublin - Head(s)? and Capital(s)?$", + "(?i)^St\\. Patrick's Cathedral, Dublin, County Dublin - Bases?$", + "(?i)^PUBLIC HEALTH VERSUS THE STATE$", + "^Viñetas de Cortázar$", + "(?i)^Search for heavy neutrinos and W(\\[|_|\\(|_\\{|-)?R(\\]|\\)|\\})? bosons with right-handed couplings in a left-right symmetric model in pp collisions at.*TeV(\\.)?$", + "(?i)^Measurement of the pseudorapidity and centrality dependence of the transverse energy density in Pb(-?)Pb collisions at.*tev(\\.?)$", + "(?i)^Search for resonances decaying into top-quark pairs using fully hadronic decays in pp collisions with ATLAS at.*TeV$", + "(?i)^Search for neutral minimal supersymmetric standard model Higgs bosons decaying to tau pairs in pp collisions at.*tev$", + "(?i)^Relatório de Estágio (de|em) Angiologia e Cirurgia Vascular$", + "^Aus der AGMB$", + "^Znanstveno-stručni prilozi$", + "(?i)^Zhodnocení finanční situace podniku a návrhy na zlepšení$", + "(?i)^Evaluation of the Financial Situation in the Firm and Proposals to its Improvement$", + "(?i)^Hodnocení finanční situace podniku a návrhy na její zlepšení$", + "^Finanční analýza podniku$", + "^Financial analysis( of business)?$", + "(?i)^Textbook of Gyn(a)?(Æ)?(e)?cology$", + "^Jikken nihon shūshinsho$", + "(?i)^CORONER('|s)(s|') INQUESTS$", + "(?i)^(Μελέτη παραγόντων )?risk management( για ανάπτυξη και εφαρμογή ενός πληροφοριακού συστήματος| και ανάπτυξη συστήματος)?$", + "(?i)^Consultants' contract(s)?$", + "(?i)^Upute autorima$", + "(?i)^Bijdrage tot de Kennis van den Godsdienst der Dajaks van Lan(d|f)ak en Tajan$", + "^Joshi shin kokubun$", + "^Kōtō shōgaku dokuhon nōson'yō$", + "^Jinjō shōgaku shōka$", + "^Shōgaku shūjichō$", + "^Nihon joshi dokuhon$", + "^Joshi shin dokuhon$", + "^Chūtō kanbun dokuhon$", + "^Wabun dokuhon$", + "(?i)^(Analysis of economy selected village or town|Rozbor hospodaření vybrané obce či města)$", + "(?i)^cardiac rehabilitation$", + "(?i)^Analytical summary$", + "^Thesaurus resolutionum Sacrae Congregationis Concilii$", + "(?i)^Sumario analítico(\\s{1})?(Analitic summary)?$", + "^Prikazi i osvrti$", + "^Rodinný dům s provozovnou$", + "^Family house with an establishment$", + "^Shinsei chūtō shin kokugun$", + "^Pulmonary alveolar proteinosis(\\.?)$", + "^Shinshū kanbun$", + "^Viñeta(s?) de Rodríguez$", + "(?i)^RUBRIKA UREDNIKA$", + "^A Matching Model of the Academic Publication Market$", + "^Yōgaku kōyō$", + "^Internetový marketing$", + "^Internet marketing$", + "^Chūtō kokugo dokuhon$", + "^Kokugo dokuhon$", + "^Antibiotic Cover for Dental Extraction(s?)$", + "^Strategie podniku$", + "^Strategy of an Enterprise$", + "(?i)^respiratory disease(s?)(\\.?)$", + "^Award(s?) for Gallantry in Civil Defence$", + "^Podniková kultura$", + "^Corporate Culture$", + "^Severe hyponatraemia in hospital inpatient(s?)(\\.?)$", + "^Pracovní motivace$", + "^Work Motivation$", + "^Kaitei kōtō jogaku dokuhon$", + "^Konsolidovaná účetní závěrka$", + "^Consolidated Financial Statements$", + "(?i)^intracranial tumour(s?)$", + "^Climate Change Mitigation Options and Directed Technical Change: A Decentralized Equilibrium Analysis$", + "^\\[CERVECERIAS MAHOU(\\.|\\:) INTERIOR\\] \\[Material gráfico\\]$", + "^Housing Market Dynamics(\\:|\\.) On the Contribution of Income Shocks and Credit Constraint(s?)$", + "^\\[Funciones auxiliares de la música en Radio París,.*\\]$", + "^Úroveň motivačního procesu jako způsobu vedení lidí$", + "^The level of motivation process as a leadership$", + "^Pay-beds in N(\\.?)H(\\.?)S(\\.?) Hospitals$", + "(?i)^news and events$", + "(?i)^NOVOSTI I DOGAĐAJI$", + "^Sansū no gakushū$", + "^Posouzení informačního systému firmy a návrh změn$", + "^Information System Assessment and Proposal for ICT Modification$", + "^Stresové zatížení pracovníků ve vybrané profesi$", + "^Stress load in a specific job$", + "^Sunday: Poster Sessions, Pt.*$", + "^Monday: Poster Sessions, Pt.*$", + "^Wednesday: Poster Sessions, Pt.*", + "^Tuesday: Poster Sessions, Pt.*$", + "^Analýza reklamy$", + "^Analysis of advertising$", + "^Shōgaku shūshinsho$", + "^Shōgaku sansū$", + "^Shintei joshi kokubun$", + "^Taishō joshi kokubun dokuhon$", + "^Joshi kokubun$", + "^Účetní uzávěrka a účetní závěrka v ČR$", + "(?i)^The \"?Causes\"? of Cancer$", + "^Normas para la publicación de artículos$", + "^Editor('|s)(s|') [Rr]eply$", + "^Editor(’|s)(s|’) letter$", + "^Redaktoriaus žodis$", + "^DISCUSSION ON THE PRECEDING PAPER$", + "^Kōtō shōgaku shūshinsho jidōyō$", + "^Shōgaku nihon rekishi$", + "^(Theory of the flow of action currents in isolated myelinated nerve fibers).*$", + "^Préface$", + "^Occupational [Hh]ealth [Ss]ervices.$", + "^In Memoriam Professor Toshiyuki TAKESHIMA$", + "^Účetní závěrka ve vybraném podniku.*$", + "^Financial statements in selected company$", + "^Abdominal [Aa]ortic [Aa]neurysms.*$", + "^Pseudomyxoma peritonei$", + "^Kazalo autora$", + "(?i)^uvodna riječ$", + "^Motivace jako způsob vedení lidí$", + "^Motivation as a leadership$", + "^Polyfunkční dům$", + "^Multi\\-funkcional building$", + "^Podnikatelský plán$", + "(?i)^Podnikatelský záměr$", + "(?i)^Business Plan$", + "^Oceňování nemovitostí$", + "^Marketingová komunikace$", + "^Marketing communication$", + "^Sumario Analítico$", + "^Riječ uredništva$", + "^Savjetovanja i priredbe$", + "^Índice$", + "^(Starobosanski nadpisi).*$", + "^Vzdělávání pracovníků v organizaci$", + "^Staff training in organization$", + "^(Life Histories of North American Geometridae).*$", + "^Strategická analýza podniku$", + "^Strategic Analysis of an Enterprise$", + "^Sadržaj$", + "^Upute suradnicima$", + "^Rodinný dům$", + "(?i)^Fami(l)?ly house$", + "^Upute autorima$", + "^Strategic Analysis$", + "^Finanční analýza vybraného podniku$", + "^Finanční analýza$", + "^Riječ urednika$", + "(?i)^Content(s?)$", + "(?i)^Inhalt$", + "^Jinjō shōgaku shūshinsho jidōyō$", + "(?i)^Index$", + "^Chūgaku kokubun kyōkasho$", + "^Retrato de una mujer$", + "^Retrato de un hombre$", + "^Kōtō shōgaku dokuhon$", + "^Shotōka kokugo$", + "^Shōgaku dokuhon$", + "^Jinjō shōgaku kokugo dokuhon$", + "^Shinsei kokugo dokuhon$", + "^Teikoku dokuhon$", + "^Instructions to Authors$", + "^KİTAP TAHLİLİ$", + "^PRZEGLĄD PIŚMIENNICTWA$", + "(?i)^Presentación$", + "^İçindekiler$", + "(?i)^Tabl?e of contents$", + "^(CODICE DEL BEATO DE LOS REYES FERNANDO I Y SANCHA).*$", + "^(\\[MADRID\\. BIBL\\. NAC\\. N.*KING FERDINAND I.*FROM SAN ISIDORO DE LEON\\. FACUNDUS SCRIPSIT DATED.*\\]).*", + "^Editorial( Board)?$", + "(?i)^Editorial \\(English\\)$", + "^Editörden$", + "^(Corpus Oral Dialectal \\(COD\\)\\.).*$", + "^(Kiri Karl Morgensternile).*$", + "^(\\[Eksliibris Aleksandr).*\\]$", + "^(\\[Eksliibris Aleksandr).*$", + "^(Eksliibris Aleksandr).*$", + "^(Kiri A\\. de Vignolles).*$", + "^(2 kirja Karl Morgensternile).*$", + "^(Pirita kloostri idaosa arheoloogilised).*$", + "^(Kiri tundmatule).*$", + "^(Kiri Jenaer Allgemeine Literaturzeitung toimetusele).*$", + "^(Eksliibris Nikolai Birukovile).*$", + "^(Eksliibris Nikolai Issakovile).*$", + "^(WHP Cruise Summary Information of section).*$", + "^(Measurement of the top quark\\-pair production cross section with ATLAS in pp collisions at).*$", + "^(Measurement of the spin\\-dependent structure function).*", + "(?i)^.*authors['’′]? reply\\.?$", + "(?i)^.*authors['’′]? response\\.?$" + ] + }, + "synonyms" : {} + } + } + + + + + + SECURITY_PARAMETERS + +
\ No newline at end of file diff --git a/dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/publication_dedup_configuration.xml b/dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/publication_dedup_configuration.xml new file mode 100644 index 000000000..e44fd29cc --- /dev/null +++ b/dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/publication_dedup_configuration.xml @@ -0,0 +1,399 @@ + +
+ + + + + +
+ + + Publication: Decision Tree Dedup - v2.0 + + { + "wf": { + "threshold": "0.99", + "dedupRun": "001", + "entityType": "result", + "subEntityType": "resulttype", + "subEntityValue": "publication", + "orderField": "title", + "queueMaxSize": "200", + "groupMaxSize": "100", + "maxChildren": "100", + "slidingWindowSize": "50", + "rootBuilder": [ + "result", + "resultProject_outcome_isProducedBy", + "resultResult_publicationDataset_isRelatedTo", + "resultResult_similarity_isAmongTopNSimilarDocuments", + "resultResult_similarity_hasAmongTopNSimilarDocuments", + "resultOrganization_affiliation_isAffiliatedWith", + "resultResult_part_hasPart", + "resultResult_part_isPartOf", + "resultResult_supplement_isSupplementTo", + "resultResult_supplement_isSupplementedBy", + "resultResult_version_isVersionOf" + ], + "includeChildren": "true", + "maxIterations": 20, + "idPath": "$.id" + }, + "pace": { + "clustering" : [ + { "name" : "wordsStatsSuffixPrefixChain", "fields" : [ "title" ], "params" : { "mod" : "10" } }, + { "name" : "lowercase", "fields" : [ "doi" ], "params" : { } } + ], + "decisionTree": { + "start": { + "fields": [ + { + "field": "pid", + "comparator": "jsonListMatch", + "weight": 1.0, + "countIfUndefined": "false", + "params": { + "jpath_value": "$.value", + "jpath_classid": "$.qualifier.classid" + } + } + ], + "threshold": 0.5, + "aggregation": "AVG", + "positive": "layer1", + "negative": "layer2", + "undefined": "layer2", + "ignoreUndefined": "true" + }, + "layer1": { + "fields": [ + { + "field": "title", + "comparator": "levensteinTitle", + "weight": 1.0, + "countIfUndefined": "true", + "params": {} + } + ], + "threshold": 0.9, + "aggregation": "AVG", + "positive": "MATCH", + "negative": "NO_MATCH", + "undefined": "NO_MATCH", + "ignoreUndefined": "true" + }, + "layer2": { + "fields": [ + { + "field": "title", + "comparator": "titleVersionMatch", + "weight": 1.0, + "countIfUndefined": "false", + "params": {} + }, + { + "field": "authors", + "comparator": "sizeMatch", + "weight": 1.0, + "countIfUndefined": "false", + "params": {} + } + ], + "threshold": 1.0, + "aggregation": "AND", + "positive": "layer3", + "negative": "NO_MATCH", + "undefined": "layer3", + "ignoreUndefined": "false" + }, + "layer3": { + "fields": [ + { + "field": "title", + "comparator": "levensteinTitle", + "weight": 1.0, + "countIfUndefined": "true", + "params": {} + } + ], + "threshold": 0.99, + "aggregation": "AVG", + "positive": "MATCH", + "negative": "NO_MATCH", + "undefined": "NO_MATCH", + "ignoreUndefined": "true" + } + }, + "model": [ + { + "name": "doi", + "type": "String", + "path": "$.pid[?(@.qualifier.classid == 'doi')].value" + }, + { + "name": "pid", + "type": "JSON", + "path": "$.pid", + "overrideMatch": "true" + }, + { + "name": "title", + "type": "String", + "path": "$.title[?(@.qualifier.classid == 'main title')].value", + "length": 250, + "size": 5 + }, + { + "name": "authors", + "type": "List", + "path": "$.author[*].fullname", + "size": 200 + }, + { + "name": "resulttype", + "type": "String", + "path": "$.resulttype.classid" + } + ], + "blacklists": { + "title": [ + "(?i)^Data Management Plan", + "^Inside Front Cover$", + "(?i)^Poster presentations$", + "^THE ASSOCIATION AND THE GENERAL MEDICAL COUNCIL$", + "^Problems with perinatal pathology\\.?$", + "(?i)^Cases? of Puerperal Convulsions$", + "(?i)^Operative Gyna?ecology$", + "(?i)^Mind the gap\\!?\\:?$", + "^Chronic fatigue syndrome\\.?$", + "^Cartas? ao editor Letters? to the Editor$", + "^Note from the Editor$", + "^Anesthesia Abstract$", + "^Annual report$", + "(?i)^“?THE RADICAL PREVENTION OF VENEREAL DISEASE\\.?”?$", + "(?i)^Graph and Table of Infectious Diseases?$", + "^Presentation$", + "(?i)^Reviews and Information on Publications$", + "(?i)^PUBLIC HEALTH SERVICES?$", + "(?i)^COMBINED TEXT-?BOOK OF OBSTETRICS AND GYN(Æ|ae)COLOGY$", + "(?i)^Adrese autora$", + "(?i)^Systematic Part .*\\. Catalogus Fossilium Austriae, Band 2: Echinoidea neogenica$", + "(?i)^Acknowledgement to Referees$", + "(?i)^Behçet's disease\\.?$", + "(?i)^Isolation and identification of restriction endonuclease.*$", + "(?i)^CEREBROVASCULAR DISEASES?.?$", + "(?i)^Screening for abdominal aortic aneurysms?\\.?$", + "^Event management$", + "(?i)^Breakfast and Crohn's disease.*\\.?$", + "^Cálculo de concentraciones en disoluciones acuosas. Ejercicio interactivo\\..*\\.$", + "(?i)^Genetic and functional analyses of SHANK2 mutations suggest a multiple hit model of Autism spectrum disorders?\\.?$", + "^Gushi hakubutsugaku$", + "^Starobosanski nadpisi u Bosni i Hercegovini \\(.*\\)$", + "^Intestinal spirocha?etosis$", + "^Treatment of Rodent Ulcer$", + "(?i)^\\W*Cloud Computing\\W*$", + "^Compendio mathematico : en que se contienen todas las materias mas principales de las Ciencias que tratan de la cantidad$", + "^Free Communications, Poster Presentations: Session [A-F]$", + "^“The Historical Aspects? of Quackery\\.?”$", + "^A designated centre for people with disabilities operated by St John of God Community Services (Limited|Ltd), Louth$", + "^P(er|re)-Mile Premiums for Auto Insurance\\.?$", + "(?i)^Case Report$", + "^Boletín Informativo$", + "(?i)^Glioblastoma Multiforme$", + "(?i)^Nuevos táxones animales descritos en la península Ibérica y Macaronesia desde 1994 \\(.*\\)$", + "^Zaměstnanecké výhody$", + "(?i)^The Economics of Terrorism and Counter-Terrorism: A Survey \\(Part .*\\)$", + "(?i)^Carotid body tumours?\\.?$", + "(?i)^\\[Españoles en Francia : La condición Emigrante.*\\]$", + "^Avant-propos$", + "(?i)^St\\. Patrick's Cathedral, Dublin, County Dublin - Head(s)? and Capital(s)?$", + "(?i)^St\\. Patrick's Cathedral, Dublin, County Dublin - Bases?$", + "(?i)^PUBLIC HEALTH VERSUS THE STATE$", + "^Viñetas de Cortázar$", + "(?i)^Search for heavy neutrinos and W(\\[|_|\\(|_\\{|-)?R(\\]|\\)|\\})? bosons with right-handed couplings in a left-right symmetric model in pp collisions at.*TeV(\\.)?$", + "(?i)^Measurement of the pseudorapidity and centrality dependence of the transverse energy density in Pb(-?)Pb collisions at.*tev(\\.?)$", + "(?i)^Search for resonances decaying into top-quark pairs using fully hadronic decays in pp collisions with ATLAS at.*TeV$", + "(?i)^Search for neutral minimal supersymmetric standard model Higgs bosons decaying to tau pairs in pp collisions at.*tev$", + "(?i)^Relatório de Estágio (de|em) Angiologia e Cirurgia Vascular$", + "^Aus der AGMB$", + "^Znanstveno-stručni prilozi$", + "(?i)^Zhodnocení finanční situace podniku a návrhy na zlepšení$", + "(?i)^Evaluation of the Financial Situation in the Firm and Proposals to its Improvement$", + "(?i)^Hodnocení finanční situace podniku a návrhy na její zlepšení$", + "^Finanční analýza podniku$", + "^Financial analysis( of business)?$", + "(?i)^Textbook of Gyn(a)?(Æ)?(e)?cology$", + "^Jikken nihon shūshinsho$", + "(?i)^CORONER('|s)(s|') INQUESTS$", + "(?i)^(Μελέτη παραγόντων )?risk management( για ανάπτυξη και εφαρμογή ενός πληροφοριακού συστήματος| και ανάπτυξη συστήματος)?$", + "(?i)^Consultants' contract(s)?$", + "(?i)^Upute autorima$", + "(?i)^Bijdrage tot de Kennis van den Godsdienst der Dajaks van Lan(d|f)ak en Tajan$", + "^Joshi shin kokubun$", + "^Kōtō shōgaku dokuhon nōson'yō$", + "^Jinjō shōgaku shōka$", + "^Shōgaku shūjichō$", + "^Nihon joshi dokuhon$", + "^Joshi shin dokuhon$", + "^Chūtō kanbun dokuhon$", + "^Wabun dokuhon$", + "(?i)^(Analysis of economy selected village or town|Rozbor hospodaření vybrané obce či města)$", + "(?i)^cardiac rehabilitation$", + "(?i)^Analytical summary$", + "^Thesaurus resolutionum Sacrae Congregationis Concilii$", + "(?i)^Sumario analítico(\\s{1})?(Analitic summary)?$", + "^Prikazi i osvrti$", + "^Rodinný dům s provozovnou$", + "^Family house with an establishment$", + "^Shinsei chūtō shin kokugun$", + "^Pulmonary alveolar proteinosis(\\.?)$", + "^Shinshū kanbun$", + "^Viñeta(s?) de Rodríguez$", + "(?i)^RUBRIKA UREDNIKA$", + "^A Matching Model of the Academic Publication Market$", + "^Yōgaku kōyō$", + "^Internetový marketing$", + "^Internet marketing$", + "^Chūtō kokugo dokuhon$", + "^Kokugo dokuhon$", + "^Antibiotic Cover for Dental Extraction(s?)$", + "^Strategie podniku$", + "^Strategy of an Enterprise$", + "(?i)^respiratory disease(s?)(\\.?)$", + "^Award(s?) for Gallantry in Civil Defence$", + "^Podniková kultura$", + "^Corporate Culture$", + "^Severe hyponatraemia in hospital inpatient(s?)(\\.?)$", + "^Pracovní motivace$", + "^Work Motivation$", + "^Kaitei kōtō jogaku dokuhon$", + "^Konsolidovaná účetní závěrka$", + "^Consolidated Financial Statements$", + "(?i)^intracranial tumour(s?)$", + "^Climate Change Mitigation Options and Directed Technical Change: A Decentralized Equilibrium Analysis$", + "^\\[CERVECERIAS MAHOU(\\.|\\:) INTERIOR\\] \\[Material gráfico\\]$", + "^Housing Market Dynamics(\\:|\\.) On the Contribution of Income Shocks and Credit Constraint(s?)$", + "^\\[Funciones auxiliares de la música en Radio París,.*\\]$", + "^Úroveň motivačního procesu jako způsobu vedení lidí$", + "^The level of motivation process as a leadership$", + "^Pay-beds in N(\\.?)H(\\.?)S(\\.?) Hospitals$", + "(?i)^news and events$", + "(?i)^NOVOSTI I DOGAĐAJI$", + "^Sansū no gakushū$", + "^Posouzení informačního systému firmy a návrh změn$", + "^Information System Assessment and Proposal for ICT Modification$", + "^Stresové zatížení pracovníků ve vybrané profesi$", + "^Stress load in a specific job$", + "^Sunday: Poster Sessions, Pt.*$", + "^Monday: Poster Sessions, Pt.*$", + "^Wednesday: Poster Sessions, Pt.*", + "^Tuesday: Poster Sessions, Pt.*$", + "^Analýza reklamy$", + "^Analysis of advertising$", + "^Shōgaku shūshinsho$", + "^Shōgaku sansū$", + "^Shintei joshi kokubun$", + "^Taishō joshi kokubun dokuhon$", + "^Joshi kokubun$", + "^Účetní uzávěrka a účetní závěrka v ČR$", + "(?i)^The \"?Causes\"? of Cancer$", + "^Normas para la publicación de artículos$", + "^Editor('|s)(s|') [Rr]eply$", + "^Editor(’|s)(s|’) letter$", + "^Redaktoriaus žodis$", + "^DISCUSSION ON THE PRECEDING PAPER$", + "^Kōtō shōgaku shūshinsho jidōyō$", + "^Shōgaku nihon rekishi$", + "^(Theory of the flow of action currents in isolated myelinated nerve fibers).*$", + "^Préface$", + "^Occupational [Hh]ealth [Ss]ervices.$", + "^In Memoriam Professor Toshiyuki TAKESHIMA$", + "^Účetní závěrka ve vybraném podniku.*$", + "^Financial statements in selected company$", + "^Abdominal [Aa]ortic [Aa]neurysms.*$", + "^Pseudomyxoma peritonei$", + "^Kazalo autora$", + "(?i)^uvodna riječ$", + "^Motivace jako způsob vedení lidí$", + "^Motivation as a leadership$", + "^Polyfunkční dům$", + "^Multi\\-funkcional building$", + "^Podnikatelský plán$", + "(?i)^Podnikatelský záměr$", + "(?i)^Business Plan$", + "^Oceňování nemovitostí$", + "^Marketingová komunikace$", + "^Marketing communication$", + "^Sumario Analítico$", + "^Riječ uredništva$", + "^Savjetovanja i priredbe$", + "^Índice$", + "^(Starobosanski nadpisi).*$", + "^Vzdělávání pracovníků v organizaci$", + "^Staff training in organization$", + "^(Life Histories of North American Geometridae).*$", + "^Strategická analýza podniku$", + "^Strategic Analysis of an Enterprise$", + "^Sadržaj$", + "^Upute suradnicima$", + "^Rodinný dům$", + "(?i)^Fami(l)?ly house$", + "^Upute autorima$", + "^Strategic Analysis$", + "^Finanční analýza vybraného podniku$", + "^Finanční analýza$", + "^Riječ urednika$", + "(?i)^Content(s?)$", + "(?i)^Inhalt$", + "^Jinjō shōgaku shūshinsho jidōyō$", + "(?i)^Index$", + "^Chūgaku kokubun kyōkasho$", + "^Retrato de una mujer$", + "^Retrato de un hombre$", + "^Kōtō shōgaku dokuhon$", + "^Shotōka kokugo$", + "^Shōgaku dokuhon$", + "^Jinjō shōgaku kokugo dokuhon$", + "^Shinsei kokugo dokuhon$", + "^Teikoku dokuhon$", + "^Instructions to Authors$", + "^KİTAP TAHLİLİ$", + "^PRZEGLĄD PIŚMIENNICTWA$", + "(?i)^Presentación$", + "^İçindekiler$", + "(?i)^Tabl?e of contents$", + "^(CODICE DEL BEATO DE LOS REYES FERNANDO I Y SANCHA).*$", + "^(\\[MADRID\\. BIBL\\. NAC\\. N.*KING FERDINAND I.*FROM SAN ISIDORO DE LEON\\. FACUNDUS SCRIPSIT DATED.*\\]).*", + "^Editorial( Board)?$", + "(?i)^Editorial \\(English\\)$", + "^Editörden$", + "^(Corpus Oral Dialectal \\(COD\\)\\.).*$", + "^(Kiri Karl Morgensternile).*$", + "^(\\[Eksliibris Aleksandr).*\\]$", + "^(\\[Eksliibris Aleksandr).*$", + "^(Eksliibris Aleksandr).*$", + "^(Kiri A\\. de Vignolles).*$", + "^(2 kirja Karl Morgensternile).*$", + "^(Pirita kloostri idaosa arheoloogilised).*$", + "^(Kiri tundmatule).*$", + "^(Kiri Jenaer Allgemeine Literaturzeitung toimetusele).*$", + "^(Eksliibris Nikolai Birukovile).*$", + "^(Eksliibris Nikolai Issakovile).*$", + "^(WHP Cruise Summary Information of section).*$", + "^(Measurement of the top quark\\-pair production cross section with ATLAS in pp collisions at).*$", + "^(Measurement of the spin\\-dependent structure function).*", + "(?i)^.*authors['’′]? reply\\.?$", + "(?i)^.*authors['’′]? response\\.?$" + ] + }, + "synonyms": {} + } + } + + + + + + SECURITY_PARAMETERS + +
\ No newline at end of file diff --git a/dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/result_deduplication_orchestrator.xml b/dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/result_deduplication_orchestrator.xml new file mode 100644 index 000000000..c2dea9672 --- /dev/null +++ b/dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/result_deduplication_orchestrator.xml @@ -0,0 +1,27 @@ + +
+ + + + + +
+ + + + + + + + + + + + + + + + + SECURITY_PARAMETERS + +
\ No newline at end of file diff --git a/dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/software_dedup_configuration.xml b/dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/software_dedup_configuration.xml new file mode 100644 index 000000000..5b0590099 --- /dev/null +++ b/dhp-workflows/dhp-workflow-profiles/src/main/resources/eu/dnetlib/dhp/wf/profiles/software_dedup_configuration.xml @@ -0,0 +1,128 @@ + +
+ + + + + +
+ + + Software: Decision Tree Dedup - v2.0 + + { + "wf" : { + "threshold" : "0.99", + "dedupRun" : "001", + "entityType" : "result", + "subEntityType" : "resulttype", + "subEntityValue" : "software", + "orderField" : "title", + "queueMaxSize" : "200", + "groupMaxSize" : "100", + "maxChildren" : "100", + "slidingWindowSize" : "50", + "rootBuilder" : [ "result", "resultProject_outcome_isProducedBy", "resultResult_publicationDataset_isRelatedTo", "resultResult_similarity_isAmongTopNSimilarDocuments", "resultResult_similarity_hasAmongTopNSimilarDocuments", "resultOrganization_affiliation_hasAuthorInstitution", "resultResult_part_hasPart", "resultResult_part_isPartOf", "resultResult_supplement_isSupplementTo", "resultResult_supplement_isSupplementedBy", "resultResult_version_isVersionOf" ], + "includeChildren" : "true" + }, + "pace" : { + "clustering" : [ + { "name" : "wordsStatsSuffixPrefixChain", "fields" : [ "title" ], "params" : { "mod" : "10" } }, + { "name" : "lowercase", "fields" : [ "doi" ], "params" : { } } + ], + "decisionTree": { + "start": { + "fields": [ + { + "field": "doi", + "comparator": "exactMatch", + "weight": 1, + "countIfUndefined": "false", + "params": {} + }, + { + "field": "url", + "comparator": "exactMatch", + "weight": 1, + "countIfUndefined": "false", + "params": {} + } + ], + "threshold": 1, + "aggregation": "OR", + "positive": "layer1", + "negative": "layer2", + "undefined": "layer2", + "ignoreUndefined": "false" + }, + "layer1": { + "fields": [ + { + "field": "title", + "comparator": "levensteinTitleIgnoreVersion", + "weight": 1, + "countIfUndefined": "false", + "params": {} + } + ], + "threshold": 0.9, + "aggregation": "AVG", + "positive": "MATCH", + "negative": "NO_MATCH", + "undefined": "NO_MATCH", + "ignoreUndefined": "false" + }, + "layer2": { + "fields": [ + { + "field": "title", + "comparator": "levensteinTitleIgnoreVersion", + "weight": 1, + "countIfUndefined": "false", + "params": {} + } + ], + "threshold": 0.99, + "aggregation": "AVG", + "positive": "MATCH", + "negative": "NO_MATCH", + "undefined": "NO_MATCH", + "ignoreUndefined": "false" + } + }, + "model" : [ + { + "name" : "doi", + "type" : "String", + "path" : "$.pid[?(@.qualifier.classid == 'doi')].value" + }, + { + "name" : "title", + "type" : "String", + "path" : "$.title[?(@.qualifier.classid == 'main title')].value", + "length" : 250, + "size" : 5 + }, + { + "name" : "url", + "type" : "String", + "path" : "$.instance.url" + }, + { + "name" : "resulttype", + "type" : "String", + "path" : "$.resulttype.classid" + } + ], + "blacklists" : {}, + "synonyms": {} + } + } + + + + + + SECURITY_PARAMETERS + +
\ No newline at end of file From 098914dcff4fa9a01d229ff610a656853c54602a Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Mon, 22 Mar 2021 11:35:02 +0100 Subject: [PATCH 169/445] fix wrong relation with source null --- .../SparkGenerateDOIBoostActionSet.scala | 16 +++++++++------- .../dnetlib/doiboost/crossref/Crossref2Oaf.scala | 13 +++++++------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/SparkGenerateDOIBoostActionSet.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/SparkGenerateDOIBoostActionSet.scala index 78477ae4d..21d3454da 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/SparkGenerateDOIBoostActionSet.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/SparkGenerateDOIBoostActionSet.scala @@ -38,37 +38,39 @@ object SparkGenerateDOIBoostActionSet { val crossRefRelation = parser.get("crossRefRelation") val dbaffiliationRelationPath = parser.get("dbaffiliationRelationPath") val dbOrganizationPath = parser.get("dbOrganizationPath") - val workingDirPath = parser.get("targetPath") val sequenceFilePath = parser.get("sFilePath") val asDataset = spark.read.load(dbDatasetPath).as[OafDataset] + .filter(p => p != null || p.getId != null) .map(d =>DoiBoostMappingUtil.fixResult(d)) .map(d=>DoiBoostMappingUtil.toActionSet(d))(Encoders.tuple(Encoders.STRING, Encoders.STRING)) -// .write.mode(SaveMode.Overwrite).save(s"$workingDirPath/actionSet") + val asPublication =spark.read.load(dbPublicationPath).as[Publication] + .filter(p => p != null || p.getId != null) .map(d=>DoiBoostMappingUtil.toActionSet(d))(Encoders.tuple(Encoders.STRING, Encoders.STRING)) -// .write.mode(SaveMode.Append).save(s"$workingDirPath/actionSet") + val asOrganization = spark.read.load(dbOrganizationPath).as[Organization] .map(d=>DoiBoostMappingUtil.toActionSet(d))(Encoders.tuple(Encoders.STRING, Encoders.STRING)) -// .write.mode(SaveMode.Append).save(s"$workingDirPath/actionSet") + val asCRelation = spark.read.load(crossRefRelation).as[Relation] + .filter(r => r!= null || (r.getSource != null && r.getTarget != null)) .map(d=>DoiBoostMappingUtil.toActionSet(d))(Encoders.tuple(Encoders.STRING, Encoders.STRING)) -// .write.mode(SaveMode.Append).save(s"$workingDirPath/actionSet") + val asRelAffiliation = spark.read.load(dbaffiliationRelationPath).as[Relation] .map(d=>DoiBoostMappingUtil.toActionSet(d))(Encoders.tuple(Encoders.STRING, Encoders.STRING)) -// .write.mode(SaveMode.Append).save(s"$workingDirPath/actionSet") + val d: Dataset[(String, String)] = asDataset.union(asPublication).union(asOrganization).union(asCRelation).union(asRelAffiliation) -// spark.read.load(s"$workingDirPath/actionSet").as[(String,String)] + d.rdd.repartition(6000).map(s => (new Text(s._1), new Text(s._2))).saveAsHadoopFile(s"$sequenceFilePath", classOf[Text], classOf[Text], classOf[SequenceFileOutputFormat[Text,Text]], classOf[GzipCodec]) diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala index 43b3f7e1c..b051177f5 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala @@ -15,7 +15,7 @@ import scala.collection.JavaConverters._ import scala.collection.mutable import scala.util.matching.Regex -import eu.dnetlib.dhp.schema.scholexplorer.OafUtils; +import eu.dnetlib.dhp.schema.scholexplorer.OafUtils case class CrossrefDT(doi: String, json:String, timestamp: Long) {} @@ -182,7 +182,7 @@ case object Crossref2Oaf { // Ticket #6281 added pid to Instance instance.setPid(result.getPid) - val has_review = (json \ "relation" \"has-review" \ "id") + val has_review = json \ "relation" \"has-review" \ "id" if(has_review != JNothing) { instance.setRefereed( @@ -208,8 +208,9 @@ case object Crossref2Oaf { instance.setUrl(links.asJava) result.setId(IdentifierFactory.createDOIBoostIdentifier(result)) if (result.getId== null) - return null - result + null + else + result } @@ -241,9 +242,9 @@ case object Crossref2Oaf { val result = generateItemFromType(objectType, objectSubType) if (result == null) return List() - val cOBJCategory = mappingCrossrefSubType.getOrElse(objectType, mappingCrossrefSubType.getOrElse(objectSubType, "0038 Other literature type")); + val cOBJCategory = mappingCrossrefSubType.getOrElse(objectType, mappingCrossrefSubType.getOrElse(objectSubType, "0038 Other literature type")) mappingResult(result, json, cOBJCategory) - if (result == null) + if (result == null || result.getId == null) return List() From c392936b9743e232a28f1b219d55b4322761da2a Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Tue, 23 Mar 2021 09:23:22 +0100 Subject: [PATCH 170/445] fixed error on best access right --- .../dnetlib/doiboost/DoiBoostMappingUtil.scala | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala index 5bd8d6636..03f6653c7 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala @@ -107,7 +107,7 @@ object DoiBoostMappingUtil { def fixResult(result: Dataset) :Dataset = { - val instanceType = result.getInstance().asScala.find(i => i.getInstancetype != null && i.getInstancetype.getClassid.nonEmpty) + val instanceType = extractInstance(result) if (instanceType.isDefined) { result.getInstance().asScala.foreach(i => i.setInstancetype(instanceType.get.getInstancetype)) } @@ -135,6 +135,11 @@ object DoiBoostMappingUtil { } + + def extractInstance(r:Result):Option[Instance] = { + r.getInstance().asScala.find(i => i.getInstancetype != null && i.getInstancetype.getClassid.nonEmpty) + } + def fixPublication(input:((String,Publication), (String,HostedByItemType))): Publication = { val publication = input._1._2 @@ -142,7 +147,7 @@ object DoiBoostMappingUtil { val item = if (input._2 != null) input._2._2 else null - val instanceType = publication.getInstance().asScala.find(i => i.getInstancetype != null && i.getInstancetype.getClassid.nonEmpty) + val instanceType:Option[Instance] = extractInstance(publication) if (instanceType.isDefined) { publication.getInstance().asScala.foreach(i => i.setInstancetype(instanceType.get.getInstancetype)) @@ -156,7 +161,8 @@ object DoiBoostMappingUtil { hb.setKey(generateDSId(item.id)) if (item.openAccess) i.setAccessright(getOpenAccessQualifier()) - publication.setBestaccessright(getOpenAccessQualifier()) + val ar = getOpenAccessQualifier() + publication.setBestaccessright(OafUtils.createQualifier(ar.getClassid, ar.getClassname, ar.getSchemeid, ar.getSchemename)) } else { hb.setValue("Unknown Repository") @@ -168,10 +174,12 @@ object DoiBoostMappingUtil { val ar = publication.getInstance().asScala.filter(i => i.getInstancetype != null && i.getAccessright!= null && i.getAccessright.getClassid!= null).map(f=> f.getAccessright.getClassid) if (ar.nonEmpty) { if(ar.contains("OPEN")){ - publication.setBestaccessright(getOpenAccessQualifier()) + val ar = getOpenAccessQualifier() + publication.setBestaccessright(OafUtils.createQualifier(ar.getClassid, ar.getClassname, ar.getSchemeid, ar.getSchemename)) } else { - publication.setBestaccessright(getRestrictedQualifier()) + val ar = getRestrictedQualifier() + publication.setBestaccessright(OafUtils.createQualifier(ar.getClassid, ar.getClassname, ar.getSchemeid, ar.getSchemename)) } } publication From 431cbe9955530806bd202cf76b30358ea55f7d6a Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 23 Mar 2021 09:28:58 +0100 Subject: [PATCH 171/445] handle missing instance.pid during bulk cleaning --- .../dhp/schema/oaf/CleaningFunctions.java | 26 +++++++++++-------- .../oa/graph/clean/CleaningFunctionTest.java | 3 ++- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index 1cee3058e..412ed408e 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -149,17 +149,21 @@ public class CleaningFunctions { if (Objects.nonNull(r.getInstance())) { for (Instance i : r.getInstance()) { - final Set pids = Sets.newHashSet(i.getPid()); - i - .setAlternateIdentifier( - Optional - .ofNullable(i.getAlternateIdentifier()) - .map( - altId -> altId - .stream() - .filter(p -> !pids.contains(p)) - .collect(Collectors.toList())) - .orElse(Lists.newArrayList())); + Optional + .ofNullable(i.getPid()) + .ifPresent(pid -> { + final Set pids = Sets.newHashSet(i.getPid()); + i + .setAlternateIdentifier( + Optional + .ofNullable(i.getAlternateIdentifier()) + .map( + altId -> altId + .stream() + .filter(p -> !pids.contains(p)) + .collect(Collectors.toList())) + .orElse(Lists.newArrayList())); + }); if (Objects.isNull(i.getAccessright()) || StringUtils.isBlank(i.getAccessright().getClassid())) { i diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java index 3a7a3ee19..0860c8bde 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java @@ -19,6 +19,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; @@ -78,7 +79,7 @@ public class CleaningFunctionTest { assertEquals("CLOSED", p_out.getInstance().get(0).getAccessright().getClassid()); assertEquals("Closed Access", p_out.getInstance().get(0).getAccessright().getClassname()); - Set pidTerms = vocabularies.getTerms("dnet:pid_types"); + Set pidTerms = vocabularies.getTerms(ModelConstants.DNET_PID_TYPES); assertTrue( p_out .getPid() From b4febed138ffc5394afabd430820e82239cf3e0f Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 23 Mar 2021 09:37:48 +0100 Subject: [PATCH 172/445] updated mapping tests as consequence of the special treatment reserved to Handle PIDs --- .../dnetlib/dhp/oa/graph/raw/MappersTest.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java index 0ab2403a3..f2786dd9d 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.util.List; import java.util.Optional; +import eu.dnetlib.dhp.schema.oaf.utils.PidType; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.BeforeEach; @@ -396,11 +397,10 @@ public class MappersTest { assertEquals(1, d.getAuthor().size()); assertEquals(1, d.getSubject().size()); assertEquals(1, d.getInstance().size()); - assertTrue(d.getPid().isEmpty()); - - assertTrue(d.getInstance().get(0).getPid().isEmpty()); - assertEquals(1, d.getInstance().get(0).getAlternateIdentifier().size()); - assertEquals("handle", d.getInstance().get(0).getAlternateIdentifier().get(0).getQualifier().getClassid()); + assertNotNull(d.getPid()); + assertEquals(1, d.getPid().size()); + assertTrue(PidType.isValid(d.getPid().get(0).getQualifier().getClassid())); + assertEquals(PidType.handle, PidType.valueOf(d.getPid().get(0).getQualifier().getClassid())); assertNotNull(d.getInstance().get(0).getUrl()); } @@ -451,7 +451,10 @@ public class MappersTest { assertEquals(1, p.getAuthor().size()); assertEquals("OPEN", p.getBestaccessright().getClassid()); - assertTrue(p.getPid().isEmpty()); + assertTrue(p.getPid().size() == 1); + assertTrue(PidType.isValid(p.getPid().get(0).getQualifier().getClassid())); + assertTrue(PidType.handle.equals(PidType.valueOf(p.getPid().get(0).getQualifier().getClassid()))); + assertEquals("hdl:11858/00-1734-0000-0003-EE73-2", p.getPid().get(0).getValue()); assertEquals("dataset", p.getResulttype().getClassname()); assertEquals(1, p.getInstance().size()); assertEquals("OPEN", p.getInstance().get(0).getAccessright().getClassid()); @@ -461,11 +464,8 @@ public class MappersTest { "http://creativecommons.org/licenses/by/3.0/de/legalcode", p.getInstance().get(0).getLicense().getValue()); assertEquals(1, p.getInstance().size()); - assertEquals(1, p.getInstance().get(0).getAlternateIdentifier().size()); - assertEquals("handle", p.getInstance().get(0).getAlternateIdentifier().get(0).getQualifier().getClassid()); - assertEquals( - "hdl:11858/00-1734-0000-0003-EE73-2", p.getInstance().get(0).getAlternateIdentifier().get(0).getValue()); - + assertNotNull(p.getInstance().get(0).getAlternateIdentifier()); + assertEquals(0, p.getInstance().get(0).getAlternateIdentifier().size()); assertEquals(1, p.getInstance().get(0).getUrl().size()); } From 625e4c29c417f1153cd2fe10e45a8dbf5d7f6c25 Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Tue, 23 Mar 2021 09:39:56 +0100 Subject: [PATCH 173/445] added model constants --- .../dnetlib/doiboost/DoiBoostMappingUtil.scala | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala index 03f6653c7..f2c63cee6 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala @@ -5,6 +5,7 @@ import eu.dnetlib.dhp.schema.oaf.{AccessRight, DataInfo, Dataset, Field, Instanc import eu.dnetlib.dhp.utils.DHPUtils import org.apache.commons.lang3.StringUtils import com.fasterxml.jackson.databind.ObjectMapper +import eu.dnetlib.dhp.schema.common.ModelConstants import eu.dnetlib.dhp.schema.scholexplorer.OafUtils import org.json4s import org.json4s.DefaultFormats @@ -112,18 +113,12 @@ object DoiBoostMappingUtil { result.getInstance().asScala.foreach(i => i.setInstancetype(instanceType.get.getInstancetype)) } result.getInstance().asScala.foreach(i => { - i.setHostedby(getUnknownHostedBy()) + i.setHostedby(ModelConstants.UNKNOWN_REPOSITORY) }) result } - def getUnknownHostedBy():KeyValue = { - val hb = new KeyValue - hb.setValue("Unknown Repository") - hb.setKey(s"10|$OPENAIRE_PREFIX::55045bd2a65019fd8e6741a755395c8c") - hb - } def getOpenAccessQualifier():AccessRight = { @@ -155,7 +150,7 @@ object DoiBoostMappingUtil { publication.getInstance().asScala.foreach(i => { - val hb = new KeyValue + var hb = new KeyValue if (item != null) { hb.setValue(item.officialname) hb.setKey(generateDSId(item.id)) @@ -165,15 +160,14 @@ object DoiBoostMappingUtil { publication.setBestaccessright(OafUtils.createQualifier(ar.getClassid, ar.getClassname, ar.getSchemeid, ar.getSchemename)) } else { - hb.setValue("Unknown Repository") - hb.setKey(s"10|$OPENAIRE_PREFIX::55045bd2a65019fd8e6741a755395c8c") + hb = ModelConstants.UNKNOWN_REPOSITORY } i.setHostedby(hb) }) val ar = publication.getInstance().asScala.filter(i => i.getInstancetype != null && i.getAccessright!= null && i.getAccessright.getClassid!= null).map(f=> f.getAccessright.getClassid) if (ar.nonEmpty) { - if(ar.contains("OPEN")){ + if(ar.contains(ModelConstants.ACCESS_RIGHT_OPEN)){ val ar = getOpenAccessQualifier() publication.setBestaccessright(OafUtils.createQualifier(ar.getClassid, ar.getClassname, ar.getSchemeid, ar.getSchemename)) } From 8db248aa13cf1fda9882b82fa84e926adc934856 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 23 Mar 2021 09:56:34 +0100 Subject: [PATCH 174/445] avoiding error on jenkins compilations: java.net.BindException: Cannot assign requested address: Service 'sparkDriver' failed after 16 retries (on a random free port)! --- .../transformation/TransformationJobTest.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java index 62a5223d9..f3a0685ac 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java @@ -34,9 +34,16 @@ import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; @ExtendWith(MockitoExtension.class) public class TransformationJobTest extends AbstractVocabularyTest { + private SparkConf sparkConf; + @BeforeEach public void setUp() throws IOException, ISLookUpException { setUpVocabulary(); + + sparkConf = new SparkConf(); + sparkConf.setMaster("local[*]"); + sparkConf.set("spark.driver.host", "localhost"); + sparkConf.set("spark.ui.enabled", "false"); } @Test @@ -124,11 +131,7 @@ public class TransformationJobTest extends AbstractVocabularyTest { @DisplayName("Test TransformSparkJobNode.main with oaiOpenaire_datacite (v4)") public void transformTestITGv4OAIdatacite(@TempDir Path testDir) throws Exception { - SparkConf conf = new SparkConf(); - conf.setAppName(TransformationJobTest.class.getSimpleName()); - conf.setMaster("local"); - - try (SparkSession spark = SparkSession.builder().config(conf).getOrCreate()) { + try (SparkSession spark = SparkSession.builder().config(sparkConf).getOrCreate()) { final String mdstore_input = this .getClass() @@ -190,11 +193,7 @@ public class TransformationJobTest extends AbstractVocabularyTest { @DisplayName("Test TransformSparkJobNode.main") public void transformTest(@TempDir Path testDir) throws Exception { - SparkConf conf = new SparkConf(); - conf.setAppName(TransformationJobTest.class.getSimpleName()); - conf.setMaster("local"); - - try (SparkSession spark = SparkSession.builder().config(conf).getOrCreate()) { + try (SparkSession spark = SparkSession.builder().config(sparkConf).getOrCreate()) { final String mdstore_input = this .getClass() From e5ebb500cf7f8d2f804e019467984e77d37d2bed Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 23 Mar 2021 12:13:53 +0100 Subject: [PATCH 175/445] fixed pom versions; included missing workflow modules in dhp-workflows/pom.xml --- dhp-workflows/dhp-stats-promote/pom.xml | 2 +- dhp-workflows/dhp-usage-raw-data-update/pom.xml | 2 +- dhp-workflows/dhp-usage-stats-build/pom.xml | 2 +- dhp-workflows/pom.xml | 3 +++ 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/dhp-workflows/dhp-stats-promote/pom.xml b/dhp-workflows/dhp-stats-promote/pom.xml index f22c19047..c64c2f58e 100644 --- a/dhp-workflows/dhp-stats-promote/pom.xml +++ b/dhp-workflows/dhp-stats-promote/pom.xml @@ -3,7 +3,7 @@ dhp-workflows eu.dnetlib.dhp - 1.2.4-branch_hadoop_aggregator-SNAPSHOT + 1.2.4-SNAPSHOT 4.0.0 dhp-stats-promote diff --git a/dhp-workflows/dhp-usage-raw-data-update/pom.xml b/dhp-workflows/dhp-usage-raw-data-update/pom.xml index 3d01ad847..a78f92d41 100644 --- a/dhp-workflows/dhp-usage-raw-data-update/pom.xml +++ b/dhp-workflows/dhp-usage-raw-data-update/pom.xml @@ -3,7 +3,7 @@ dhp-workflows eu.dnetlib.dhp - 1.2.4-branch_hadoop_aggregator-SNAPSHOT + 1.2.4-SNAPSHOT 4.0.0 dhp-usage-raw-data-update diff --git a/dhp-workflows/dhp-usage-stats-build/pom.xml b/dhp-workflows/dhp-usage-stats-build/pom.xml index bf580ed7f..20d2f5b76 100644 --- a/dhp-workflows/dhp-usage-stats-build/pom.xml +++ b/dhp-workflows/dhp-usage-stats-build/pom.xml @@ -3,7 +3,7 @@ dhp-workflows eu.dnetlib.dhp - 1.2.4-branch_hadoop_aggregator-SNAPSHOT + 1.2.4-SNAPSHOT 4.0.0 dhp-usage-stats-build diff --git a/dhp-workflows/pom.xml b/dhp-workflows/pom.xml index 190c9847e..ec8f9268c 100644 --- a/dhp-workflows/pom.xml +++ b/dhp-workflows/pom.xml @@ -28,6 +28,9 @@ dhp-graph-provision-scholexplorer dhp-blacklist dhp-stats-update + dhp-stats-promote + dhp-usage-stats-build + dhp-usage-raw-data-update dhp-broker-events dhp-doiboost From 1e423fdc0768ea1a3d1b9a7bee4bd50a77a8f9b7 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 23 Mar 2021 13:39:24 +0100 Subject: [PATCH 176/445] [Actionmanager] remove invalid records from the input graph before groupGraphTableByIdAndMerge --- .../actionmanager/promote/PromoteActionPayloadFunctions.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadFunctions.java b/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadFunctions.java index 56c8dd05a..c0192cddb 100644 --- a/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadFunctions.java +++ b/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadFunctions.java @@ -111,7 +111,9 @@ public class PromoteActionPayloadFunctions { SerializableSupplier> isNotZeroFn, Class rowClazz) { TypedColumn aggregator = new TableAggregator<>(zeroFn, mergeAndGetFn, isNotZeroFn, rowClazz).toColumn(); + return rowDS + .filter((FilterFunction) o -> isNotZeroFn.get().apply(o)) .groupByKey((MapFunction) x -> rowIdFn.get().apply(x), Encoders.STRING()) .agg(aggregator) .map((MapFunction, G>) Tuple2::_2, Encoders.kryo(rowClazz)); From 751125fdf95b9435f5ef86769a9d5b9623cfca14 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 23 Mar 2021 17:34:32 +0100 Subject: [PATCH 177/445] [Actionmanager] zero function considers empty entity.id as well as rel.source/rel.target --- .../promote/PromoteActionPayloadForGraphTableJob.java | 11 ++++++----- .../promote/PromoteActionPayloadFunctions.java | 1 - .../java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadForGraphTableJob.java b/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadForGraphTableJob.java index bab4377bd..0052026d4 100644 --- a/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadForGraphTableJob.java +++ b/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadForGraphTableJob.java @@ -5,12 +5,12 @@ import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; import static eu.dnetlib.dhp.schema.common.ModelSupport.isSubClass; import java.io.IOException; -import java.util.Objects; import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.spark.SparkConf; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.sql.Dataset; @@ -194,7 +194,7 @@ public class PromoteActionPayloadForGraphTableJob { SerializableSupplier> mergeRowWithActionPayloadAndGetFn = MergeAndGet.functionFor(strategy); SerializableSupplier> mergeRowsAndGetFn = MergeAndGet.functionFor(strategy); SerializableSupplier zeroFn = zeroFn(rowClazz); - SerializableSupplier> isNotZeroFn = PromoteActionPayloadForGraphTableJob::isNotZeroFnUsingIdOrSource; + SerializableSupplier> isNotZeroFn = PromoteActionPayloadForGraphTableJob::isNotZeroFnUsingIdOrSourceAndTarget; Dataset joinedAndMerged = PromoteActionPayloadFunctions .joinGraphTableWithActionPayloadAndMerge( @@ -238,12 +238,13 @@ public class PromoteActionPayloadForGraphTableJob { } } - private static Function isNotZeroFnUsingIdOrSource() { + private static Function isNotZeroFnUsingIdOrSourceAndTarget() { return t -> { if (isSubClass(t, Relation.class)) { - return Objects.nonNull(((Relation) t).getSource()); + final Relation rel = (Relation) t; + return StringUtils.isNotBlank(rel.getSource()) && StringUtils.isNotBlank(rel.getTarget()); } - return Objects.nonNull(((OafEntity) t).getId()); + return StringUtils.isNotBlank(((OafEntity) t).getId()); }; } diff --git a/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadFunctions.java b/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadFunctions.java index c0192cddb..d799c646b 100644 --- a/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadFunctions.java +++ b/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/promote/PromoteActionPayloadFunctions.java @@ -111,7 +111,6 @@ public class PromoteActionPayloadFunctions { SerializableSupplier> isNotZeroFn, Class rowClazz) { TypedColumn aggregator = new TableAggregator<>(zeroFn, mergeAndGetFn, isNotZeroFn, rowClazz).toColumn(); - return rowDS .filter((FilterFunction) o -> isNotZeroFn.get().apply(o)) .groupByKey((MapFunction) x -> rowIdFn.get().apply(x), Encoders.STRING()) diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java index f2786dd9d..c86e31280 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/raw/MappersTest.java @@ -11,7 +11,6 @@ import java.io.IOException; import java.util.List; import java.util.Optional; -import eu.dnetlib.dhp.schema.oaf.utils.PidType; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.BeforeEach; @@ -33,6 +32,7 @@ import eu.dnetlib.dhp.schema.oaf.Publication; import eu.dnetlib.dhp.schema.oaf.Relation; import eu.dnetlib.dhp.schema.oaf.Software; import eu.dnetlib.dhp.schema.oaf.StructuredProperty; +import eu.dnetlib.dhp.schema.oaf.utils.PidType; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; @ExtendWith(MockitoExtension.class) From 348b0ef9217a8264f325841ee0aaf28176590abb Mon Sep 17 00:00:00 2001 From: miconis Date: Wed, 24 Mar 2021 15:51:27 +0100 Subject: [PATCH 178/445] bug fix, implementation of the workflow for the creation of raw_organizations (openorgs dedup), addition of the pid lists to the openorgs postgres db --- .../dhp/oa/dedup/AbstractSparkAction.java | 16 ++ .../dhp/oa/dedup/SparkPrepareNewOrgs.java | 10 +- .../dhp/oa/dedup/SparkPrepareOrgRels.java | 26 +- .../dnetlib/dhp/oa/dedup/model/OrgSimRel.java | 14 +- .../raw/MigrateDbEntitiesApplication.java | 7 + .../oa/graph/raw/common/MigrateAction.java | 3 +- .../oozie_app/config-default.xml | 18 ++ .../raw_organizations/oozie_app/workflow.xml | 270 ++++++++++++++++++ .../dhp/oa/graph/sql/queryOrganizations.sql | 4 +- .../sql/queryOrganizationsFromOpenOrgsDB.sql | 4 + 10 files changed, 354 insertions(+), 18 deletions(-) create mode 100644 dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_organizations/oozie_app/config-default.xml create mode 100644 dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_organizations/oozie_app/workflow.xml diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java index 9a1127764..28f6e3107 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java @@ -6,7 +6,9 @@ import java.io.Serializable; import java.io.StringReader; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.apache.spark.SparkConf; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.SaveMode; @@ -31,6 +33,9 @@ abstract class AbstractSparkAction implements Serializable { protected static final int NUM_PARTITIONS = 1000; protected static final int NUM_CONNECTIONS = 20; + protected static final String TYPE_VALUE_SEPARATOR = "###"; + protected static final String SP_SEPARATOR = "@@@"; + protected static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); @@ -97,4 +102,15 @@ abstract class AbstractSparkAction implements Serializable { protected static void removeOutputDir(SparkSession spark, String path) { HdfsSupport.remove(path, spark.sparkContext().hadoopConfiguration()); } + + protected static String structuredPropertyListToString(List list) { + + return list + .stream() + .filter(p -> p.getQualifier() != null) + .filter(p -> StringUtils.isNotBlank(p.getQualifier().getClassid())) + .filter(p -> StringUtils.isNotBlank(p.getValue())) + .map(p -> p.getValue() + TYPE_VALUE_SEPARATOR + p.getQualifier().getClassid()) + .collect(Collectors.joining(SP_SEPARATOR)); + } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java index 3b29e1e17..465f56c83 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java @@ -12,7 +12,6 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaPairRDD; -import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.function.FilterFunction; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.sql.Dataset; @@ -107,8 +106,9 @@ public class SparkPrepareNewOrgs extends AbstractSparkAction { .mode(SaveMode.Append) .jdbc(dbUrl, dbTable, connectionProperties); - if (!apiUrl.isEmpty()) - updateSimRels(apiUrl); + // TODO de-comment once finished +// if (!apiUrl.isEmpty()) +// updateSimRels(apiUrl); } @@ -181,7 +181,9 @@ public class SparkPrepareNewOrgs extends AbstractSparkAction { r._1()._2().getLegalshortname() != null ? r._1()._2().getLegalshortname().getValue() : "", r._1()._2().getCountry() != null ? r._1()._2().getCountry().getClassid() : "", r._1()._2().getWebsiteurl() != null ? r._1()._2().getWebsiteurl().getValue() : "", - r._1()._2().getCollectedfrom().get(0).getValue(), ""), + r._1()._2().getCollectedfrom().get(0).getValue(), + "", + structuredPropertyListToString(r._1()._2().getPid())), Encoders.bean(OrgSimRel.class)); } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java index cbca0b326..e2d9ae9c6 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java @@ -14,6 +14,7 @@ import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoders; import org.apache.spark.sql.SaveMode; import org.apache.spark.sql.SparkSession; +import org.apache.spark.util.LongAccumulator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -209,15 +210,19 @@ public class SparkPrepareOrgRels extends AbstractSparkAction { Dataset> relations2 = relations .joinWith(entities, relations.col("_2").equalTo(entities.col("_1")), "inner") .map( - (MapFunction, Tuple2>, OrgSimRel>) r -> new OrgSimRel( - r._1()._1(), - r._2()._2().getOriginalId().get(0), - r._2()._2().getLegalname() != null ? r._2()._2().getLegalname().getValue() : "", - r._2()._2().getLegalshortname() != null ? r._2()._2().getLegalshortname().getValue() : "", - r._2()._2().getCountry() != null ? r._2()._2().getCountry().getClassid() : "", - r._2()._2().getWebsiteurl() != null ? r._2()._2().getWebsiteurl().getValue() : "", - r._2()._2().getCollectedfrom().get(0).getValue(), - r._1()._3()), + (MapFunction, Tuple2>, OrgSimRel>) r -> { + + return new OrgSimRel( + r._1()._1(), + r._2()._2().getOriginalId().get(0), + r._2()._2().getLegalname() != null ? r._2()._2().getLegalname().getValue() : "", + r._2()._2().getLegalshortname() != null ? r._2()._2().getLegalshortname().getValue() : "", + r._2()._2().getCountry() != null ? r._2()._2().getCountry().getClassid() : "", + r._2()._2().getWebsiteurl() != null ? r._2()._2().getWebsiteurl().getValue() : "", + r._2()._2().getCollectedfrom().get(0).getValue(), + r._1()._3(), + structuredPropertyListToString(r._2()._2().getPid())); + }, Encoders.bean(OrgSimRel.class)) .map( (MapFunction>) o -> new Tuple2<>(o.getLocal_id(), o), @@ -311,7 +316,8 @@ public class SparkPrepareOrgRels extends AbstractSparkAction { r._2()._2().getCountry() != null ? r._2()._2().getCountry().getClassid() : "", r._2()._2().getWebsiteurl() != null ? r._2()._2().getWebsiteurl().getValue() : "", r._2()._2().getCollectedfrom().get(0).getValue(), - "group::" + r._1()._1()), + "group::" + r._1()._1(), + structuredPropertyListToString(r._2()._2().getPid())), Encoders.bean(OrgSimRel.class)) .map( (MapFunction>) o -> new Tuple2<>(o.getLocal_id(), o), diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/OrgSimRel.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/OrgSimRel.java index 65f383500..adff1ab8a 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/OrgSimRel.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/OrgSimRel.java @@ -13,12 +13,13 @@ public class OrgSimRel implements Serializable { String oa_url; String oa_collectedfrom; String group_id; + String pid_list; // separator for type-pid: "###"; separator for pids: "@@@" public OrgSimRel() { } public OrgSimRel(String local_id, String oa_original_id, String oa_name, String oa_acronym, String oa_country, - String oa_url, String oa_collectedfrom, String group_id) { + String oa_url, String oa_collectedfrom, String group_id, String pid_list) { this.local_id = local_id; this.oa_original_id = oa_original_id; this.oa_name = oa_name; @@ -27,6 +28,7 @@ public class OrgSimRel implements Serializable { this.oa_url = oa_url; this.oa_collectedfrom = oa_collectedfrom; this.group_id = group_id; + this.pid_list = pid_list; } public String getLocal_id() { @@ -93,6 +95,14 @@ public class OrgSimRel implements Serializable { this.group_id = group_id; } + public String getPid_list() { + return pid_list; + } + + public void setPid_list(String pid_list) { + this.pid_list = pid_list; + } + @Override public String toString() { return "OrgSimRel{" + @@ -103,6 +113,8 @@ public class OrgSimRel implements Serializable { ", oa_country='" + oa_country + '\'' + ", oa_url='" + oa_url + '\'' + ", oa_collectedfrom='" + oa_collectedfrom + '\'' + + ", group_id='" + group_id + '\'' + + ", pid_list='" + pid_list + '\'' + '}'; } } diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java index 3e5030eaa..4d7de6f7f 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java @@ -164,6 +164,13 @@ public class MigrateDbEntitiesApplication extends AbstractMigrationApplication i log.info("Processing Openorgs Merge Rels..."); smdbe.execute("querySimilarityFromOpenOrgsDB.sql", smdbe::processOrgOrgSimRels); + break; + + case openaire_organizations: + + log.info("Processing Organizations..."); + smdbe.execute("queryOrganizations.sql", smdbe::processOrganization, verifyNamespacePrefix); + break; } log.info("All done."); diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/MigrateAction.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/MigrateAction.java index d9ee9bb6a..06ebeb994 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/MigrateAction.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/MigrateAction.java @@ -5,5 +5,6 @@ package eu.dnetlib.dhp.oa.graph.raw.common; public enum MigrateAction { claims, // migrate claims to the raw graph openorgs, // migrate organizations from openorgs to the raw graph - openaire // migrate openaire entities to the raw graph + openaire, // migrate openaire entities to the raw graph + openaire_organizations // migrate openaire organizations entities to the raw graph } diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_organizations/oozie_app/config-default.xml b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_organizations/oozie_app/config-default.xml new file mode 100644 index 000000000..2e0ed9aee --- /dev/null +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_organizations/oozie_app/config-default.xml @@ -0,0 +1,18 @@ + + + jobTracker + yarnRM + + + nameNode + hdfs://nameservice1 + + + oozie.use.system.libpath + true + + + oozie.action.sharelib.for.spark + spark2 + + \ No newline at end of file diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_organizations/oozie_app/workflow.xml b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_organizations/oozie_app/workflow.xml new file mode 100644 index 000000000..95b66dc34 --- /dev/null +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_organizations/oozie_app/workflow.xml @@ -0,0 +1,270 @@ + + + + + graphOutputPath + the target path to store raw graph + + + reuseContent + false + should import content from the aggregator or reuse a previous version + + + contentPath + path location to store (or reuse) content from the aggregator + + + postgresURL + the postgres URL to access to the database + + + postgresUser + the user postgres + + + postgresPassword + the password postgres + + + postgresOpenOrgsURL + the postgres URL to access to the OpenOrgs database + + + postgresOpenOrgsUser + the user of OpenOrgs database + + + postgresOpenOrgsPassword + the password of OpenOrgs database + + + dbSchema + beta + the database schema according to the D-Net infrastructure (beta or production) + + + isLookupUrl + the address of the lookUp service + + + nsPrefixBlacklist + + a blacklist of nsprefixes (comma separeted) + + + sparkDriverMemory + memory for driver process + + + sparkExecutorMemory + memory for individual executor + + + sparkExecutorCores + number of cores used by single executor + + + oozieActionShareLibForSpark2 + oozie action sharelib for spark 2.* + + + spark2ExtraListeners + com.cloudera.spark.lineage.NavigatorAppListener + spark 2.* extra listeners classname + + + spark2SqlQueryExecutionListeners + com.cloudera.spark.lineage.NavigatorQueryListener + spark 2.* sql query execution listeners classname + + + spark2YarnHistoryServerAddress + spark 2.* yarn history server address + + + spark2EventLogDir + spark 2.* event log dir location + + + + + ${jobTracker} + ${nameNode} + + + mapreduce.job.queuename + ${queueName} + + + oozie.launcher.mapred.job.queue.name + ${oozieLauncherQueueName} + + + oozie.action.sharelib.for.spark + ${oozieActionShareLibForSpark2} + + + + + + + + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] + + + + + ${wf:conf('reuseContent') eq false} + ${wf:conf('reuseContent') eq true} + + + + + + + + + + + + + + + eu.dnetlib.dhp.oa.graph.raw.MigrateDbEntitiesApplication + --hdfsPath${contentPath}/db_openaire_organizations + --postgresUrl${postgresURL} + --postgresUser${postgresUser} + --postgresPassword${postgresPassword} + --isLookupUrl${isLookupUrl} + --actionopenaire_organizations + --dbschema${dbSchema} + --nsPrefixBlacklist${nsPrefixBlacklist} + + + + + + + + + + + eu.dnetlib.dhp.oa.graph.raw.MigrateDbEntitiesApplication + --hdfsPath${contentPath}/db_openorgs + --postgresUrl${postgresOpenOrgsURL} + --postgresUser${postgresOpenOrgsUser} + --postgresPassword${postgresOpenOrgsPassword} + --isLookupUrl${isLookupUrl} + --actionopenorgs + --dbschema${dbSchema} + --nsPrefixBlacklist${nsPrefixBlacklist} + + + + + + + + + + yarn + cluster + GenerateEntities + eu.dnetlib.dhp.oa.graph.raw.GenerateEntitiesApplication + dhp-graph-mapper-${projectVersion}.jar + + --executor-memory ${sparkExecutorMemory} + --executor-cores ${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + + --sourcePaths${contentPath}/db_openaire_organizations,${contentPath}/db_openorgs + --targetPath${workingDir}/entities + --isLookupUrl${isLookupUrl} + + + + + + + + yarn + cluster + GenerateGraph + eu.dnetlib.dhp.oa.graph.raw.DispatchEntitiesApplication + dhp-graph-mapper-${projectVersion}.jar + + --executor-memory ${sparkExecutorMemory} + --executor-cores ${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.shuffle.partitions=7680 + + --sourcePath${workingDir}/entities + --graphRawPath${graphOutputPath} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizations.sql b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizations.sql index 938744b11..9a8f98931 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizations.sql +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizations.sql @@ -24,7 +24,7 @@ SELECT d.officialname AS collectedfromname, o.country || '@@@dnet:countries' AS country, 'sysimport:crosswalk:entityregistry@@@dnet:provenance_actions' AS provenanceaction, - array_remove(array_agg(DISTINCT i.pid || '###' || i.issuertype), NULL) AS pid + array_agg(DISTINCT i.pid || '###' || i.issuertype || '@@@dnet:pid_types') AS pid FROM dsm_organizations o LEFT OUTER JOIN dsm_datasources d ON (d.id = o.collectedfrom) LEFT OUTER JOIN dsm_organizationpids p ON (p.organization = o.id) @@ -50,4 +50,4 @@ GROUP BY o.trust, d.id, d.officialname, - o.country + o.country; diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizationsFromOpenOrgsDB.sql b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizationsFromOpenOrgsDB.sql index 82ece5a1c..dbe0c136b 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizationsFromOpenOrgsDB.sql +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizationsFromOpenOrgsDB.sql @@ -31,6 +31,8 @@ FROM organizations o LEFT OUTER JOIN urls u ON (u.id = o.id) LEFT OUTER JOIN other_ids i ON (i.id = o.id) LEFT OUTER JOIN other_names n ON (n.id = o.id) +WHERE + o.status = 'approved' GROUP BY o.id, o.name, @@ -72,6 +74,8 @@ FROM other_names n LEFT OUTER JOIN organizations o ON (n.id = o.id) LEFT OUTER JOIN urls u ON (u.id = o.id) LEFT OUTER JOIN other_ids i ON (i.id = o.id) +WHERE + o.status = 'approved' GROUP BY o.id, o.creation_date, From 5dfb66b0fa13d1a6f74f161b7208dc72f33da207 Mon Sep 17 00:00:00 2001 From: miconis Date: Thu, 25 Mar 2021 10:29:34 +0100 Subject: [PATCH 179/445] minor changes --- .../java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java index 465f56c83..950676677 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java @@ -106,9 +106,8 @@ public class SparkPrepareNewOrgs extends AbstractSparkAction { .mode(SaveMode.Append) .jdbc(dbUrl, dbTable, connectionProperties); - // TODO de-comment once finished -// if (!apiUrl.isEmpty()) -// updateSimRels(apiUrl); + if (!apiUrl.isEmpty()) + updateSimRels(apiUrl); } From 827e7e37db7fa9774bb4d692d9ed160ce2837f39 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 25 Mar 2021 11:07:59 +0100 Subject: [PATCH 180/445] [Cleaning] drop instance.alternateIdentifier elements when they are available among instance.pid --- .../dhp/schema/oaf/CleaningFunctions.java | 14 +--- .../oa/graph/clean/CleaningFunctionTest.java | 64 +++++++++++++++++-- .../eu/dnetlib/dhp/oa/graph/clean/result.json | 44 +++++++++++++ 3 files changed, 107 insertions(+), 15 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index 412ed408e..afbe0cff6 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -152,17 +152,9 @@ public class CleaningFunctions { Optional .ofNullable(i.getPid()) .ifPresent(pid -> { - final Set pids = Sets.newHashSet(i.getPid()); - i - .setAlternateIdentifier( - Optional - .ofNullable(i.getAlternateIdentifier()) - .map( - altId -> altId - .stream() - .filter(p -> !pids.contains(p)) - .collect(Collectors.toList())) - .orElse(Lists.newArrayList())); + final Set pids = Sets.newHashSet(pid); + final Set altIds = Sets.newHashSet(i.getAlternateIdentifier()); + i.setAlternateIdentifier(Lists.newArrayList(Sets.difference(altIds, pids))); }); if (Objects.isNull(i.getAccessright()) || StringUtils.isBlank(i.getAccessright().getClassid())) { diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java index 0860c8bde..fdbc58c17 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java @@ -87,11 +87,67 @@ public class CleaningFunctionTest { .map(p -> p.getQualifier()) .allMatch(q -> pidTerms.contains(q.getClassid()))); - Publication p_defaults = CleaningFunctions.cleanup(p_out); - assertEquals("CLOSED", p_defaults.getBestaccessright().getClassid()); + List poi = p_out.getInstance(); + assertNotNull(poi); + assertEquals(1, poi.size()); + + final Instance poii = poi.get(0); + assertNotNull(poii); + assertNotNull(poii.getPid()); + + assertEquals(2, poii.getPid().size()); + + assertTrue( + poii.getPid().stream().filter(s -> s.getValue().equals("10.1007/s109090161569x")).findFirst().isPresent()); + assertTrue(poii.getPid().stream().filter(s -> s.getValue().equals("10.1008/abcd")).findFirst().isPresent()); + + assertNotNull(poii.getAlternateIdentifier()); + assertEquals(2, poii.getAlternateIdentifier().size()); + + assertTrue( + poii + .getAlternateIdentifier() + .stream() + .filter(s -> s.getValue().equals("10.1007/s109090161569x")) + .findFirst() + .isPresent()); + assertTrue( + poii + .getAlternateIdentifier() + .stream() + .filter(s -> s.getValue().equals("10.1009/qwerty")) + .findFirst() + .isPresent()); + + Publication p_cleaned = CleaningFunctions.cleanup(p_out); + assertEquals("CLOSED", p_cleaned.getBestaccessright().getClassid()); assertNull(p_out.getPublisher()); - getAuthorPids(p_defaults).forEach(pid -> { + final List pci = p_cleaned.getInstance(); + assertNotNull(pci); + assertEquals(1, pci.size()); + + final Instance pcii = pci.get(0); + assertNotNull(pcii); + assertNotNull(pcii.getPid()); + + assertEquals(2, pcii.getPid().size()); + + assertTrue( + pcii.getPid().stream().filter(s -> s.getValue().equals("10.1007/s109090161569x")).findFirst().isPresent()); + assertTrue(pcii.getPid().stream().filter(s -> s.getValue().equals("10.1008/abcd")).findFirst().isPresent()); + + assertNotNull(pcii.getAlternateIdentifier()); + assertEquals(1, pcii.getAlternateIdentifier().size()); + assertTrue( + pcii + .getAlternateIdentifier() + .stream() + .filter(s -> s.getValue().equals("10.1009/qwerty")) + .findFirst() + .isPresent()); + + getAuthorPids(p_cleaned).forEach(pid -> { System.out .println( String @@ -101,7 +157,7 @@ public class CleaningFunctionTest { }); // TODO add more assertions to verity the cleaned values - System.out.println(MAPPER.writeValueAsString(p_out)); + System.out.println(MAPPER.writeValueAsString(p_cleaned)); /* * assertTrue( p_out .getPid() .stream() .allMatch(sp -> StringUtils.isNotBlank(sp.getValue()))); diff --git a/dhp-workflows/dhp-graph-mapper/src/test/resources/eu/dnetlib/dhp/oa/graph/clean/result.json b/dhp-workflows/dhp-graph-mapper/src/test/resources/eu/dnetlib/dhp/oa/graph/clean/result.json index e746d236e..23de2ef86 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/resources/eu/dnetlib/dhp/oa/graph/clean/result.json +++ b/dhp-workflows/dhp-graph-mapper/src/test/resources/eu/dnetlib/dhp/oa/graph/clean/result.json @@ -318,6 +318,50 @@ "id": "50|CSC_________::2250a70c903c6ac6e4c01438259e9375", "instance": [ { + "pid": [ + { + "dataInfo": null, + "qualifier": { + "classid": "doi", + "classname": "doi", + "schemeid": "dnet:pid_types", + "schemename": "dnet:pid_types" + }, + "value": "10.1007/s109090161569x" + }, + { + "dataInfo": null, + "qualifier": { + "classid": "doi", + "classname": "doi", + "schemeid": "dnet:pid_types", + "schemename": "dnet:pid_types" + }, + "value": "10.1008/abcd" + } + ], + "alternateIdentifier": [ + { + "dataInfo": null, + "qualifier": { + "classid": "doi", + "classname": "doi", + "schemeid": "dnet:pid_types", + "schemename": "dnet:pid_types" + }, + "value": "10.1007/s109090161569x" + }, + { + "dataInfo": null, + "qualifier": { + "classid": "doi", + "classname": "doi", + "schemeid": "dnet:pid_types", + "schemename": "dnet:pid_types" + }, + "value": "10.1009/qwerty" + } + ], "accessright": { "classid": "CLOSED", "classname": "CLOSED", From b5b7dc210401c1a9de68aad3c7ff9212cf355bb6 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Fri, 26 Mar 2021 12:30:00 +0100 Subject: [PATCH 181/445] [Cleaning] drop alternate identifiers with empty values --- .../java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index afbe0cff6..6c7d3e915 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -152,7 +152,12 @@ public class CleaningFunctions { Optional .ofNullable(i.getPid()) .ifPresent(pid -> { - final Set pids = Sets.newHashSet(pid); + final Set pids = Sets + .newHashSet( + pid + .stream() + .filter(p -> StringUtils.isBlank(p.getValue())) + .collect(Collectors.toList())); final Set altIds = Sets.newHashSet(i.getAlternateIdentifier()); i.setAlternateIdentifier(Lists.newArrayList(Sets.difference(altIds, pids))); }); From 1dfda3624e6c030ee88c51e7c2f9a526ad1ae3c7 Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Fri, 26 Mar 2021 13:56:29 +0100 Subject: [PATCH 182/445] improved workflow importing datacite --- .../datacite/AbstractRestClient.scala | 5 +- .../datacite/DataciteAPIImporter.scala | 10 +- .../DataciteToOAFTransformation.scala | 3 +- .../datacite/ImportDatacite.scala | 92 +++++++++++-------- .../datacite/import_from_api.json | 6 ++ .../datacite/oozie_app/workflow.xml | 8 +- .../SparkGenerateDOIBoostActionSet.scala | 2 +- .../crossref/CrossrefMappingTest.scala | 11 +++ 8 files changed, 90 insertions(+), 47 deletions(-) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/AbstractRestClient.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/AbstractRestClient.scala index 3c7770075..8df203283 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/AbstractRestClient.scala +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/AbstractRestClient.scala @@ -57,10 +57,13 @@ abstract class AbstractRestClient extends Iterator[String]{ private def doHTTPRequest[A <: HttpUriRequest](r: A) :String ={ val client = HttpClients.createDefault + var tries = 4 try { - var tries = 4 while (tries > 0) { + + println(s"requesting ${r.getURI}") val response = client.execute(r) + println(s"get response with status${response.getStatusLine.getStatusCode}") if (response.getStatusLine.getStatusCode > 400) { tries -= 1 } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteAPIImporter.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteAPIImporter.scala index c2ad6855c..36ec9e8c3 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteAPIImporter.scala +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteAPIImporter.scala @@ -3,7 +3,7 @@ package eu.dnetlib.dhp.actionmanager.datacite import org.json4s.{DefaultFormats, JValue} import org.json4s.jackson.JsonMethods.{compact, parse, render} -class DataciteAPIImporter(timestamp: Long = 0, blocks: Long = 10) extends AbstractRestClient { +class DataciteAPIImporter(timestamp: Long = 0, blocks: Long = 10, until:Long = -1) extends AbstractRestClient { override def extractInfo(input: String): Unit = { implicit lazy val formats: DefaultFormats.type = org.json4s.DefaultFormats @@ -16,9 +16,15 @@ class DataciteAPIImporter(timestamp: Long = 0, blocks: Long = 10) extends Abstra current_index = 0 } + def get_url():String ={ + val to = if (until> 0) s"$until" else "*" + s"https://api.datacite.org/dois?page[cursor]=1&page[size]=$blocks&query=updated:[$timestamp%20TO%20$to]" + + } + override def getBufferData(): Unit = { if (!complete) { - val response = if (scroll_value.isDefined) doHTTPGETRequest(scroll_value.get) else doHTTPGETRequest(s"https://api.datacite.org/dois?page[cursor]=1&page[size]=$blocks&query=updated:[$timestamp%20TO%20*]") + val response = if (scroll_value.isDefined) doHTTPGETRequest(scroll_value.get) else doHTTPGETRequest(get_url()) extractInfo(response) } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala index 1ae1f086e..1776a4ad6 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala @@ -164,9 +164,8 @@ object DataciteToOAFTransformation { case _: Throwable => try { return Some(LocalDate.parse(a_date, df_it).toString) } catch { - case _: Throwable => try { + case _: Throwable => return None - } } } } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ImportDatacite.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ImportDatacite.scala index d5edb674a..6cec4ea34 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ImportDatacite.scala +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ImportDatacite.scala @@ -1,5 +1,6 @@ package eu.dnetlib.dhp.actionmanager.datacite +import eu.dnetlib.dhp.actionmanager.datacite.DataciteToOAFTransformation.df_it import eu.dnetlib.dhp.application.ArgumentApplicationParser import org.apache.hadoop.conf.Configuration import org.apache.hadoop.fs.{FileSystem, LocalFileSystem, Path} @@ -15,7 +16,7 @@ import org.apache.spark.sql.functions.max import org.slf4j.{Logger, LoggerFactory} import java.time.format.DateTimeFormatter._ -import java.time.{LocalDateTime, ZoneOffset} +import java.time.{LocalDate, LocalDateTime, ZoneOffset} import scala.io.Source object ImportDatacite { @@ -23,21 +24,20 @@ object ImportDatacite { val log: Logger = LoggerFactory.getLogger(ImportDatacite.getClass) - def convertAPIStringToDataciteItem(input:String): DataciteType = { + def convertAPIStringToDataciteItem(input: String): DataciteType = { implicit lazy val formats: DefaultFormats.type = org.json4s.DefaultFormats lazy val json: org.json4s.JValue = parse(input) val doi = (json \ "attributes" \ "doi").extract[String].toLowerCase val isActive = (json \ "attributes" \ "isActive").extract[Boolean] - val timestamp_string = (json \ "attributes" \ "updated").extract[String] + val timestamp_string = (json \ "attributes" \ "updated").extract[String] val dt = LocalDateTime.parse(timestamp_string, ISO_DATE_TIME) - DataciteType(doi = doi, timestamp = dt.toInstant(ZoneOffset.UTC).toEpochMilli/1000, isActive = isActive, json = input) + DataciteType(doi = doi, timestamp = dt.toInstant(ZoneOffset.UTC).toEpochMilli / 1000, isActive = isActive, json = input) } - def main(args: Array[String]): Unit = { val parser = new ArgumentApplicationParser(Source.fromInputStream(getClass.getResourceAsStream("/eu/dnetlib/dhp/actionmanager/datacite/import_from_api.json")).mkString) @@ -53,9 +53,13 @@ object ImportDatacite { val dataciteDump = parser.get("dataciteDumpPath") log.info(s"dataciteDump is $dataciteDump") - val hdfsTargetPath =new Path(targetPath) + val hdfsTargetPath = new Path(targetPath) log.info(s"hdfsTargetPath is $hdfsTargetPath") + + val spkipImport = parser.get("skipImport") + log.info(s"skipImport is $spkipImport") + val spark: SparkSession = SparkSession.builder() .appName(ImportDatacite.getClass.getSimpleName) .master(master) @@ -69,7 +73,7 @@ object ImportDatacite { // Because of Maven conf.set("fs.hdfs.impl", classOf[DistributedFileSystem].getName) conf.set("fs.file.impl", classOf[LocalFileSystem].getName) - val sc:SparkContext = spark.sparkContext + val sc: SparkContext = spark.sparkContext sc.setLogLevel("ERROR") import spark.implicits._ @@ -84,14 +88,14 @@ object ImportDatacite { return a if (a == null) return b - if(a.timestamp >b.timestamp) { + if (a.timestamp > b.timestamp) { return a } b } override def merge(a: DataciteType, b: DataciteType): DataciteType = { - reduce(a,b) + reduce(a, b) } override def bufferEncoder: Encoder[DataciteType] = implicitly[Encoder[DataciteType]] @@ -101,69 +105,77 @@ object ImportDatacite { override def finish(reduction: DataciteType): DataciteType = reduction } - val dump:Dataset[DataciteType] = spark.read.load(dataciteDump).as[DataciteType] + val dump: Dataset[DataciteType] = spark.read.load(dataciteDump).as[DataciteType] val ts = dump.select(max("timestamp")).first().getLong(0) - log.info(s"last Timestamp is $ts") + println(s"last Timestamp is $ts") - val cnt = writeSequenceFile(hdfsTargetPath, ts, conf) + val cnt = if ("true".equalsIgnoreCase(spkipImport)) 1 else writeSequenceFile(hdfsTargetPath, ts, conf) + println(s"Imported from Datacite API $cnt documents") - log.info(s"Imported from Datacite API $cnt documents") + if (cnt > 0) { - if (cnt > 0) { - - val inputRdd:RDD[DataciteType] = sc.sequenceFile(targetPath, classOf[Int], classOf[Text]) + val inputRdd: RDD[DataciteType] = sc.sequenceFile(targetPath, classOf[Int], classOf[Text]) .map(s => s._2.toString) .map(s => convertAPIStringToDataciteItem(s)) spark.createDataset(inputRdd).write.mode(SaveMode.Overwrite).save(s"${targetPath}_dataset") - val ds:Dataset[DataciteType] = spark.read.load(s"${targetPath}_dataset").as[DataciteType] + val ds: Dataset[DataciteType] = spark.read.load(s"${targetPath}_dataset").as[DataciteType] dump .union(ds) .groupByKey(_.doi) .agg(dataciteAggregator.toColumn) - .map(s=>s._2) + .map(s => s._2) .repartition(4000) .write.mode(SaveMode.Overwrite).save(s"${dataciteDump}_updated") val fs = FileSystem.get(sc.hadoopConfiguration) fs.delete(new Path(s"$dataciteDump"), true) - fs.rename(new Path(s"${dataciteDump}_updated"),new Path(s"$dataciteDump")) + fs.rename(new Path(s"${dataciteDump}_updated"), new Path(s"$dataciteDump")) } } - private def writeSequenceFile(hdfsTargetPath: Path, timestamp: Long, conf: Configuration):Long = { - val client = new DataciteAPIImporter(timestamp*1000, 1000) + private def writeSequenceFile(hdfsTargetPath: Path, timestamp: Long, conf: Configuration): Long = { + var from:Long = timestamp * 1000 + val delta:Long = 50000000L + var client: DataciteAPIImporter = null + val now :Long =System.currentTimeMillis() var i = 0 try { val writer = SequenceFile.createWriter(conf, SequenceFile.Writer.file(hdfsTargetPath), SequenceFile.Writer.keyClass(classOf[IntWritable]), SequenceFile.Writer.valueClass(classOf[Text])) try { - var start: Long = System.currentTimeMillis - var end: Long = 0 - val key: IntWritable = new IntWritable(i) - val value: Text = new Text - while ( { - client.hasNext - }) { - key.set({ - i += 1; - i - 1 - }) - value.set(client.next()) - writer.append(key, value) - writer.hflush() - if (i % 1000 == 0) { - end = System.currentTimeMillis - val time = (end - start) / 1000.0F - println(s"Imported $i in $time seconds") - start = System.currentTimeMillis + while (from < now) { + client = new DataciteAPIImporter(from, 1000, from + delta) + var end: Long = 0 + val key: IntWritable = new IntWritable(i) + val value: Text = new Text + while (client.hasNext) { + key.set({ + i += 1; + i - 1 + }) + value.set(client.next()) + writer.append(key, value) + writer.hflush() + if (i % 1000 == 0) { + end = System.currentTimeMillis + val time = (end - start) / 1000.0F + println(s"Imported $i in $time seconds") + start = System.currentTimeMillis + } } + println(s"updating from value: $from -> ${from+delta}") + from = from + delta } + } catch { + case e: Throwable => + println("Error", e) } finally if (writer != null) writer.close() } i } + } \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/import_from_api.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/import_from_api.json index 967e4445a..69fb039ba 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/import_from_api.json +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/import_from_api.json @@ -12,6 +12,12 @@ "paramDescription": "the path of the Datacite dump", "paramRequired": true }, + { + "paramName": "s", + "paramLongName": "skipImport", + "paramDescription": "avoid to downlaod new items but apply the previous update", + "paramRequired": false + }, { "paramName": "n", "paramLongName": "namenode", diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/workflow.xml index 047794c9c..15378c6c7 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/workflow.xml @@ -13,6 +13,11 @@ nativeInputPath the path of the input MDStore + + skipimport + false + the path of the input MDStore + @@ -51,6 +56,7 @@ -t${nativeInputPath} -d${mdstoreInputPath} -n${nameNode} + -s${skipimport} --masteryarn-cluster @@ -81,7 +87,7 @@ -tr${isLookupUrl} --masteryarn-cluster - + diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/SparkGenerateDOIBoostActionSet.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/SparkGenerateDOIBoostActionSet.scala index 21d3454da..3bfca0859 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/SparkGenerateDOIBoostActionSet.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/SparkGenerateDOIBoostActionSet.scala @@ -57,7 +57,7 @@ object SparkGenerateDOIBoostActionSet { val asCRelation = spark.read.load(crossRefRelation).as[Relation] - .filter(r => r!= null || (r.getSource != null && r.getTarget != null)) + .filter(r => r!= null && r.getSource != null && r.getTarget != null) .map(d=>DoiBoostMappingUtil.toActionSet(d))(Encoders.tuple(Encoders.STRING, Encoders.STRING)) diff --git a/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/crossref/CrossrefMappingTest.scala b/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/crossref/CrossrefMappingTest.scala index 4568e23a5..cc112528e 100644 --- a/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/crossref/CrossrefMappingTest.scala +++ b/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/crossref/CrossrefMappingTest.scala @@ -59,6 +59,17 @@ class CrossrefMappingTest { } + @Test + def testSum() :Unit = { + val from:Long = 1613135645000L + val delta:Long = 1000000L + + + println(s"updating from value: $from -> ${from+delta}") + + + } + @Test def testOrcidID() :Unit = { val json = Source.fromInputStream(getClass.getResourceAsStream("orcid_data.json")).mkString From 2355cc4e9b4bd5547d256434780b9db6cdd31ece Mon Sep 17 00:00:00 2001 From: miconis Date: Mon, 29 Mar 2021 10:07:12 +0200 Subject: [PATCH 183/445] minor changes and bug fix --- .../oaf/utils/OrganizationPidComparator.java | 5 ++ .../dnetlib/dhp/schema/oaf/utils/PidType.java | 2 +- .../dhp/oa/dedup/AbstractSparkAction.java | 4 ++ .../dhp/oa/dedup/DedupRecordFactory.java | 2 +- .../eu/dnetlib/dhp/oa/dedup/IdGenerator.java | 9 ++- .../oa/dedup/SparkCopyOpenorgsMergeRels.java | 55 +++++++++--------- .../oa/dedup/SparkCopyOpenorgsSimRels.java | 2 +- .../dhp/oa/dedup/SparkCreateSimRels.java | 2 +- .../dhp/oa/dedup/SparkUpdateEntity.java | 26 ++++++--- .../dhp/oa/dedup/scan/oozie_app/workflow.xml | 57 +++++++++++++------ .../dhp/oa/dedup/EntityMergerTest.java | 2 +- .../dnetlib/dhp/oa/dedup/IdGeneratorTest.java | 11 ++++ .../dnetlib/dhp/oa/dedup/SparkDedupTest.java | 23 +++++--- .../dhp/oa/dedup/SparkOpenorgsTest.java | 5 +- .../dedup/json/organization_idgeneration.json | 3 + .../raw/MigrateDbEntitiesApplication.java | 39 ++++++++----- .../oa/graph/raw/common/MigrateAction.java | 3 +- .../raw_organizations/oozie_app/workflow.xml | 2 +- ...gsDB.sql => queryOpenOrgsForOrgsDedup.sql} | 0 .../graph/sql/queryOpenOrgsForProvision.sql | 41 +++++++++++++ ...> queryOpenOrgsSimilarityForOrgsDedup.sql} | 8 +-- .../queryOpenOrgsSimilarityForProvision.sql | 12 ++++ 22 files changed, 224 insertions(+), 89 deletions(-) create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/organization_idgeneration.json rename dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/{queryOrganizationsFromOpenOrgsDB.sql => queryOpenOrgsForOrgsDedup.sql} (100%) create mode 100644 dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsForProvision.sql rename dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/{querySimilarityFromOpenOrgsDB.sql => queryOpenOrgsSimilarityForOrgsDedup.sql} (89%) create mode 100644 dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsSimilarityForProvision.sql diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java index 57285fb82..3a6df2924 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/OrganizationPidComparator.java @@ -13,6 +13,11 @@ public class OrganizationPidComparator implements Comparator PidType lClass = PidType.tryValueOf(left.getQualifier().getClassid()); PidType rClass = PidType.tryValueOf(right.getQualifier().getClassid()); + if (lClass.equals(PidType.openorgs)) + return -1; + if (rClass.equals(PidType.openorgs)) + return 1; + if (lClass.equals(PidType.GRID)) return -1; if (rClass.equals(PidType.GRID)) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidType.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidType.java index 62f682026..5a297be5e 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidType.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/utils/PidType.java @@ -9,7 +9,7 @@ public enum PidType { doi, pmid, pmc, handle, arXiv, nct, pdb, // Organization - GRID, mag_id, urn, + openorgs, corda, corda_h2020, GRID, mag_id, urn, // Used by dedup undefined, original; diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java index 28f6e3107..708d67f6e 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java @@ -99,6 +99,10 @@ abstract class AbstractSparkAction implements Serializable { dataset.write().option("compression", "gzip").mode(mode).json(outPath); } + protected static void saveParquet(Dataset dataset, String outPath, SaveMode mode) { + dataset.write().option("compression", "gzip").mode(mode).parquet(outPath); + } + protected static void removeOutputDir(SparkSession spark, String path) { HdfsSupport.remove(path, spark.sparkContext().hadoopConfiguration()); } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java index 99cd7c31f..fe9bd74ce 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupRecordFactory.java @@ -89,7 +89,7 @@ public class DedupRecordFactory { t -> { T duplicate = t._2(); - // prepare the list of pids to use for the id generation + // prepare the list of pids to be used for the id generation bestPids.add(Identifier.newInstance(duplicate)); entity.mergeFrom(duplicate); diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java index 51e54ee4f..dd9b16790 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/IdGenerator.java @@ -36,7 +36,14 @@ public class IdGenerator implements Serializable { } private static String dedupify(String ns) { - StringBuilder prefix = new StringBuilder(substringBefore(ns, "_")).append("_dedup"); + + StringBuilder prefix; + if (PidType.valueOf(substringBefore(ns, "_")) == PidType.openorgs) { + prefix = new StringBuilder(substringBefore(ns, "_")); + } else { + prefix = new StringBuilder(substringBefore(ns, "_")).append("_dedup"); + } + while (prefix.length() < 12) { prefix.append("_"); } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java index 201043a08..6bd1a00b9 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java @@ -23,12 +23,13 @@ import eu.dnetlib.dhp.schema.oaf.DataInfo; import eu.dnetlib.dhp.schema.oaf.KeyValue; import eu.dnetlib.dhp.schema.oaf.Qualifier; import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.dhp.utils.DHPUtils; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; import eu.dnetlib.pace.config.DedupConfig; +import net.sf.saxon.ma.trie.Tuple2; -//copy simrels (verified) from relation to the workdir in order to make them available for the deduplication public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { private static final Logger log = LoggerFactory.getLogger(SparkCopyOpenorgsMergeRels.class); public static final String PROVENANCE_ACTION_CLASS = "sysimport:dedup"; @@ -84,24 +85,32 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { .map(patchRelFn(), Encoders.bean(Relation.class)) .toJavaRDD() .filter(this::isOpenorgs) - .filter(this::filterOpenorgsRels) - .filter(this::excludeOpenorgsMesh) - .filter(this::excludeNonOpenorgs); // excludes relations with no openorgs id involved + .filter(this::filterOpenorgsRels); + + JavaRDD selfRawRels = rawRels + .map(r -> r.getSource()) + .distinct() + .map(s -> rel(s, s, "isSimilarTo", dedupConf)); log.info("Number of raw Openorgs Relations collected: {}", rawRels.count()); // turn openorgs isSimilarTo relations into mergerels - JavaRDD mergeRelsRDD = rawRels.flatMap(rel -> { - List mergerels = new ArrayList<>(); + JavaRDD mergeRelsRDD = rawRels + .union(selfRawRels) + .map(r -> { + r.setSource(createDedupID(r.getSource())); // create the dedup_id to align it to the openaire dedup + // format + return r; + }) + .flatMap(rel -> { - String openorgsId = rel.getSource().contains("openorgs____") ? rel.getSource() : rel.getTarget(); - String mergedId = rel.getSource().contains("openorgs____") ? rel.getTarget() : rel.getSource(); + List mergerels = new ArrayList<>(); - mergerels.add(rel(openorgsId, mergedId, "merges", dedupConf)); - mergerels.add(rel(mergedId, openorgsId, "isMergedIn", dedupConf)); + mergerels.add(rel(rel.getSource(), rel.getTarget(), "merges", dedupConf)); + mergerels.add(rel(rel.getTarget(), rel.getSource(), "isMergedIn", dedupConf)); - return mergerels.iterator(); - }); + return mergerels.iterator(); + }); log.info("Number of Openorgs Merge Relations created: {}", mergeRelsRDD.count()); @@ -144,22 +153,6 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { return false; } - private boolean excludeOpenorgsMesh(Relation rel) { - - if (rel.getSource().contains("openorgsmesh") || rel.getTarget().contains("openorgsmesh")) { - return false; - } - return true; - } - - private boolean excludeNonOpenorgs(Relation rel) { - - if (rel.getSource().contains("openorgs____") || rel.getTarget().contains("openorgs____")) { - return true; - } - return false; - } - private Relation rel(String source, String target, String relClass, DedupConfig dedupConf) { String entityType = dedupConf.getWf().getEntityType(); @@ -189,4 +182,10 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { r.setDataInfo(info); return r; } + + public String createDedupID(String id) { + + String prefix = id.split("\\|")[0]; + return prefix + "|dedup_wf_001::" + DHPUtils.md5(id); + } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java index b7f88a5f6..8cffacd7e 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java @@ -82,7 +82,7 @@ public class SparkCopyOpenorgsSimRels extends AbstractSparkAction { .map(patchRelFn(), Encoders.bean(Relation.class)) .filter(this::filterOpenorgsRels); - save(rawRels, outputPath, SaveMode.Append); + saveParquet(rawRels, outputPath, SaveMode.Append); log.info("Copied " + rawRels.count() + " Similarity Relations"); } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateSimRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateSimRels.java index 6963312e0..96693ebf0 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateSimRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateSimRels.java @@ -109,7 +109,7 @@ public class SparkCreateSimRels extends AbstractSparkAction { .rdd(), Encoders.bean(Relation.class)); - save(simRels, outputPath, SaveMode.Append); + saveParquet(simRels, outputPath, SaveMode.Append); log.info("Generated " + simRels.count() + " Similarity Relations"); diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkUpdateEntity.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkUpdateEntity.java index 779fb91d6..5ebc00d5a 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkUpdateEntity.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkUpdateEntity.java @@ -13,6 +13,7 @@ import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaPairRDD; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; +import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.api.java.function.PairFunction; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoders; @@ -22,11 +23,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.common.EntityType; import eu.dnetlib.dhp.schema.common.ModelSupport; -import eu.dnetlib.dhp.schema.oaf.DataInfo; -import eu.dnetlib.dhp.schema.oaf.Oaf; -import eu.dnetlib.dhp.schema.oaf.OafEntity; -import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.dhp.schema.oaf.*; +import eu.dnetlib.dhp.schema.oaf.utils.PidType; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; import eu.dnetlib.pace.util.MapDocumentUtil; @@ -103,12 +103,22 @@ public class SparkUpdateEntity extends AbstractSparkAction { MapDocumentUtil.getJPathString(IDJSONPATH, s), s)); JavaRDD map = entitiesWithId .leftOuterJoin(mergedIds) - .map( - k -> k._2()._2().isPresent() - ? updateDeletedByInference(k._2()._1(), clazz) - : k._2()._1()); + .map(k -> { + if (k._2()._2().isPresent()) { + return updateDeletedByInference(k._2()._1(), clazz); + } + return k._2()._1(); + }); + + if (type == EntityType.organization) // exclude openorgs with deletedbyinference=true + map = map.filter(it -> { + Organization org = OBJECT_MAPPER.readValue(it, Organization.class); + return !org.getId().contains("openorgs____") || (org.getId().contains("openorgs____") + && !org.getDataInfo().getDeletedbyinference()); + }); sourceEntity = map.union(sc.textFile(dedupRecordPath)); + } sourceEntity.saveAsTextFile(outputPath, GzipCodec.class); diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml index c28a2a921..4b39cb56a 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml @@ -83,7 +83,7 @@ - + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] @@ -98,16 +98,14 @@ - - - - - - - - - - + + + + + + + + @@ -213,17 +211,16 @@ --actionSetId${actionSetIdOpenorgs} --numPartitions8000 - + - - + yarn cluster - Copy Openorgs Entities - eu.dnetlib.dhp.oa.dedup.SparkCopyOpenorgs + Create Organizations Dedup Records + eu.dnetlib.dhp.oa.dedup.SparkCreateDedupRecord dhp-dedup-openaire-${projectVersion}.jar --executor-memory=${sparkExecutorMemory} @@ -237,12 +234,40 @@ --graphBasePath${graphBasePath} --workingPath${workingPath} + --isLookUpUrl${isLookUpUrl} --actionSetId${actionSetIdOpenorgs} + + + + + + + + + + + + + + + + + + + + + + + + + + + yarn diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java index 3f10af5b8..787295c41 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/EntityMergerTest.java @@ -112,7 +112,7 @@ public class EntityMergerTest implements Serializable { assertEquals("2018-09-30", pub_merged.getDateofacceptance().getValue()); // verify authors - assertEquals(9, pub_merged.getAuthor().size()); + assertEquals(13, pub_merged.getAuthor().size()); assertEquals(4, AuthorMerger.countAuthorsPids(pub_merged.getAuthor())); // verify title diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java index a6604dd30..6b0b8dfa2 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/IdGeneratorTest.java @@ -36,6 +36,8 @@ public class IdGeneratorTest { private static List> bestIds2; private static List> bestIds3; + private static List> bestIdsOrg; + private static String testEntityBasePath; @BeforeAll @@ -48,6 +50,8 @@ public class IdGeneratorTest { bestIds = createBestIds(testEntityBasePath + "/publication_idgeneration.json", Publication.class); bestIds2 = createBestIds(testEntityBasePath + "/publication_idgeneration2.json", Publication.class); bestIds3 = createBestIds(testEntityBasePath + "/publication_idgeneration3.json", Publication.class); + + bestIdsOrg = createBestIds(testEntityBasePath + "/organization_idgeneration.json", Organization.class); } @Test @@ -76,6 +80,13 @@ public class IdGeneratorTest { assertEquals("50|dedup_wf_001::0829b5191605bdbea36d6502b8c1ce1g", id2); } + @Test + public void generateIdOrganizationTest() { + String id1 = IdGenerator.generate(bestIdsOrg, "20|defaultID"); + + assertEquals("20|openorgs____::599c15a70fcb03be6ba08f75f14d6076", id1); + } + protected static List> createBestIds(String path, Class clazz) { final Stream> ids = readSample(path, clazz) .stream() diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java index c706061a0..33da45feb 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java @@ -174,27 +174,27 @@ public class SparkDedupTest implements Serializable { long orgs_simrel = spark .read() - .load(testOutputBasePath + "/" + testActionSetId + "/organization_simrel") + .load(DedupUtility.createSimRelPath(testOutputBasePath, testActionSetId, "organization")) .count(); long pubs_simrel = spark .read() - .load(testOutputBasePath + "/" + testActionSetId + "/publication_simrel") + .load(DedupUtility.createSimRelPath(testOutputBasePath, testActionSetId, "publication")) .count(); long sw_simrel = spark .read() - .load(testOutputBasePath + "/" + testActionSetId + "/software_simrel") + .load(DedupUtility.createSimRelPath(testOutputBasePath, testActionSetId, "software")) .count(); long ds_simrel = spark .read() - .load(testOutputBasePath + "/" + testActionSetId + "/dataset_simrel") + .load(DedupUtility.createSimRelPath(testOutputBasePath, testActionSetId, "dataset")) .count(); long orp_simrel = spark .read() - .load(testOutputBasePath + "/" + testActionSetId + "/otherresearchproduct_simrel") + .load(DedupUtility.createSimRelPath(testOutputBasePath, testActionSetId, "otherresearchproduct")) .count(); assertEquals(3082, orgs_simrel); @@ -204,6 +204,7 @@ public class SparkDedupTest implements Serializable { assertEquals(6750, orp_simrel); } + @Disabled @Test @Order(2) public void collectSimRelsTest() throws Exception { @@ -254,9 +255,15 @@ public class SparkDedupTest implements Serializable { long orp_simrel = spark .read() - .load(testOutputBasePath + "/" + testActionSetId + "/otherresearchproduct_simrel") + .json(testOutputBasePath + "/" + testActionSetId + "/otherresearchproduct_simrel") .count(); +// System.out.println("orgs_simrel = " + orgs_simrel); +// System.out.println("pubs_simrel = " + pubs_simrel); +// System.out.println("sw_simrel = " + sw_simrel); +// System.out.println("ds_simrel = " + ds_simrel); +// System.out.println("orp_simrel = " + orp_simrel); + assertEquals(3672, orgs_simrel); assertEquals(10459, pubs_simrel); assertEquals(3767, sw_simrel); @@ -456,7 +463,7 @@ public class SparkDedupTest implements Serializable { testOutputBasePath + "/" + testActionSetId + "/otherresearchproduct_deduprecord") .count(); - assertEquals(84, orgs_deduprecord); + assertEquals(85, orgs_deduprecord); assertEquals(65, pubs_deduprecord); assertEquals(51, sw_deduprecord); assertEquals(97, ds_deduprecord); @@ -540,7 +547,7 @@ public class SparkDedupTest implements Serializable { .count(); assertEquals(896, publications); - assertEquals(837, organizations); + assertEquals(838, organizations); assertEquals(100, projects); assertEquals(100, datasource); assertEquals(200, softwares); diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java index 6ad2145a9..7aaed3de7 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java @@ -110,6 +110,7 @@ public class SparkOpenorgsTest implements Serializable { "/eu/dnetlib/dhp/dedup/conf/org.curr.conf.json"))); } + @Disabled @Test public void copyOpenorgsTest() throws Exception { @@ -162,7 +163,7 @@ public class SparkOpenorgsTest implements Serializable { .load(testOutputBasePath + "/" + testActionSetId + "/organization_mergerel") .count(); - assertEquals(6, orgs_mergerel); + assertEquals(384, orgs_mergerel); } @@ -191,7 +192,7 @@ public class SparkOpenorgsTest implements Serializable { .textFile(testOutputBasePath + "/" + testActionSetId + "/organization_simrel") .count(); - assertEquals(96, orgs_simrel); + assertEquals(73, orgs_simrel); } @Test diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/organization_idgeneration.json b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/organization_idgeneration.json new file mode 100644 index 000000000..7e8ec63c7 --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/json/organization_idgeneration.json @@ -0,0 +1,3 @@ +{"eclegalbody": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}, "ecresearchorganization": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}, "legalname": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "Universitas Dr Soetomo"}, "pid": [], "websiteurl": null, "oaiprovenance": null, "logourl": null, "collectedfrom": [{"dataInfo": null, "value": "DOAJ-Articles", "key": "10|driver______::bee53aa31dc2cbb538c10c2b65fa5824"}], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "alternativeNames": [], "echighereducation": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}, "id": "20|doajarticles::0af3389716873a78a03f2316de09845b", "eclegalperson": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}, "lastupdatetimestamp": 1616749318035, "ecinternationalorganizationeurinterests": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}, "dateofcollection": "2020-05-25", "dateoftransformation": "2020-05-25", "ecnonprofit": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}, "ecenterprise": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}, "ecinternationalorganization": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}, "ecnutscode": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}, "legalshortname": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "Universitas Dr Soetomo"}, "country": {"classid": "ID", "classname": "Indonesia", "schemename": "dnet:countries", "schemeid": "dnet:countries"}, "extraInfo": [], "originalId": ["doajarticles::Universitas_Dr_Soetomo"], "ecsmevalidated": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}} +{"eclegalbody": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}, "ecresearchorganization": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}, "legalname": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "University of DR Soetomo"}, "pid": [], "websiteurl": null, "oaiprovenance": null, "logourl": null, "collectedfrom": [{"dataInfo": null, "value": "DOAJ-Articles", "key": "10|driver______::bee53aa31dc2cbb538c10c2b65fa5824"}], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "alternativeNames": [], "echighereducation": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}, "id": "20|doajarticles::4a639ae8f8668ea44699e98ee5a8f1b9", "eclegalperson": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}, "lastupdatetimestamp": 1616749318035, "ecinternationalorganizationeurinterests": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}, "dateofcollection": "2018-09-18", "dateoftransformation": "2018-09-18", "ecnonprofit": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}, "ecenterprise": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}, "ecinternationalorganization": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}, "ecnutscode": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}, "legalshortname": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "University of DR Soetomo"}, "country": {"classid": "ID", "classname": "Indonesia", "schemename": "dnet:countries", "schemeid": "dnet:countries"}, "extraInfo": [], "originalId": ["doajarticles::University_of_DR_Soetomo"], "ecsmevalidated": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.900"}, "value": "false"}} +{"eclegalbody": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.950"}, "value": "false"}, "ecresearchorganization": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.950"}, "value": "false"}, "legalname": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.950"}, "value": "Universitas Dr. Soetomo"}, "pid": [{"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.950"}, "qualifier": {"classid": "ISNI", "classname": "International Standard Name Identifier", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "0000 0004 1758 8103"}, {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.950"}, "qualifier": {"classid": "GRID", "classname": "GRID", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "grid.444390.e"}, {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.950"}, "qualifier": {"classid": "ROR", "classname": "ROR", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "https://ror.org/04s03g948"}, {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.950"}, "qualifier": {"classid": "Wikidata", "classname": "Wikidata", "schemename": "dnet:pid_types", "schemeid": "dnet:pid_types"}, "value": "Q12523318"}], "websiteurl": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.950"}, "value": "https://unitomo.ac.id/"}, "oaiprovenance": null, "logourl": null, "collectedfrom": [{"dataInfo": null, "value": "OpenOrgs Database", "key": "10|openaire____::0362fcdb3076765d9c0041ad331553e8"}], "dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.950"}, "alternativeNames": [], "echighereducation": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.950"}, "value": "false"}, "id": "20|openorgs____::599c15a70fcb03be6ba08f75f14d6076", "eclegalperson": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.950"}, "value": "false"}, "lastupdatetimestamp": 1616749318824, "ecinternationalorganizationeurinterests": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.950"}, "value": "false"}, "dateofcollection": "2020-07-16", "dateoftransformation": "2020-07-16", "ecnonprofit": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.950"}, "value": "false"}, "ecenterprise": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.950"}, "value": "false"}, "ecinternationalorganization": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.950"}, "value": "false"}, "ecnutscode": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.950"}, "value": "false"}, "legalshortname": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.950"}, "value": "UNITOMO"}, "country": {"classid": "ID", "classname": "Indonesia", "schemename": "dnet:countries", "schemeid": "dnet:countries"}, "extraInfo": [], "originalId": ["openorgs____::0000034824"], "ecsmevalidated": {"dataInfo": {"deletedbyinference": false, "provenanceaction": {"classid": "sysimport:crosswalk:entityregistry", "classname": "sysimport:crosswalk:entityregistry", "schemename": "dnet:provenanceActions", "schemeid": "dnet:provenanceActions"}, "inferred": false, "inferenceprovenance": "", "invisible": false, "trust": "0.950"}, "value": "false"}} \ No newline at end of file diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java index 1776689bd..823fd83d3 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java @@ -163,14 +163,25 @@ public class MigrateDbEntitiesApplication extends AbstractMigrationApplication i .execute( "queryProjectOrganization.sql", smdbe::processProjectOrganization, verifyNamespacePrefix); break; - case openorgs: + case openorgs_dedup: log.info("Processing Openorgs..."); smdbe .execute( - "queryOrganizationsFromOpenOrgsDB.sql", smdbe::processOrganization, verifyNamespacePrefix); + "queryOpenOrgsForOrgsDedup.sql", smdbe::processOrganization, verifyNamespacePrefix); log.info("Processing Openorgs Merge Rels..."); - smdbe.execute("querySimilarityFromOpenOrgsDB.sql", smdbe::processOrgOrgSimRels); + smdbe.execute("queryOpenOrgsSimilarityForOrgsDedup.sql", smdbe::processOrgOrgSimRels); + + break; + + case openorgs: + log.info("Processing Openorgs For Provision..."); + smdbe + .execute( + "queryOpenOrgsForProvision.sql", smdbe::processOrganization, verifyNamespacePrefix); + + log.info("Processing Openorgs Merge Rels..."); + smdbe.execute("queryOpenOrgsSimilarityForProvision.sql", smdbe::processOrgOrgSimRels); break; @@ -647,17 +658,19 @@ public class MigrateDbEntitiesApplication extends AbstractMigrationApplication i r1.setDataInfo(info); r1.setLastupdatetimestamp(lastUpdateTimestamp); - final Relation r2 = new Relation(); - r2.setRelType(ORG_ORG_RELTYPE); - r2.setSubRelType(ORG_ORG_SUBRELTYPE); - r2.setRelClass(relClass); - r2.setSource(orgId2); - r2.setTarget(orgId1); - r2.setCollectedfrom(collectedFrom); - r2.setDataInfo(info); - r2.setLastupdatetimestamp(lastUpdateTimestamp); + // removed because there's no difference between two sides //TODO +// final Relation r2 = new Relation(); +// r2.setRelType(ORG_ORG_RELTYPE); +// r2.setSubRelType(ORG_ORG_SUBRELTYPE); +// r2.setRelClass(relClass); +// r2.setSource(orgId2); +// r2.setTarget(orgId1); +// r2.setCollectedfrom(collectedFrom); +// r2.setDataInfo(info); +// r2.setLastupdatetimestamp(lastUpdateTimestamp); +// return Arrays.asList(r1, r2); - return Arrays.asList(r1, r2); + return Arrays.asList(r1); } catch (final Exception e) { throw new RuntimeException(e); } diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/MigrateAction.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/MigrateAction.java index 06ebeb994..517cc8d62 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/MigrateAction.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/common/MigrateAction.java @@ -4,7 +4,8 @@ package eu.dnetlib.dhp.oa.graph.raw.common; //enum to specify the different actions available for the MigrateDbEntitiesApplication job public enum MigrateAction { claims, // migrate claims to the raw graph - openorgs, // migrate organizations from openorgs to the raw graph + openorgs_dedup, // migrate organizations from openorgs to the raw graph + openorgs, // migrate organization from openorgs to the raw graph for provision openaire, // migrate openaire entities to the raw graph openaire_organizations // migrate openaire organizations entities to the raw graph } diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_organizations/oozie_app/workflow.xml b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_organizations/oozie_app/workflow.xml index 95b66dc34..714d69697 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_organizations/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_organizations/oozie_app/workflow.xml @@ -156,7 +156,7 @@ --postgresUser${postgresOpenOrgsUser} --postgresPassword${postgresOpenOrgsPassword} --isLookupUrl${isLookupUrl} - --actionopenorgs + --actionopenorgs_dedup --dbschema${dbSchema} --nsPrefixBlacklist${nsPrefixBlacklist} diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizationsFromOpenOrgsDB.sql b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsForOrgsDedup.sql similarity index 100% rename from dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizationsFromOpenOrgsDB.sql rename to dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsForOrgsDedup.sql diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsForProvision.sql b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsForProvision.sql new file mode 100644 index 000000000..6f5f93789 --- /dev/null +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsForProvision.sql @@ -0,0 +1,41 @@ +SELECT + o.id AS organizationid, + coalesce((array_agg(a.acronym))[1], o.name) AS legalshortname, + o.name AS legalname, + array_agg(DISTINCT n.name) AS "alternativeNames", + (array_agg(u.url))[1] AS websiteurl, + '' AS logourl, + o.creation_date AS dateofcollection, + o.modification_date AS dateoftransformation, + false AS inferred, + false AS deletedbyinference, + 0.95 AS trust, + '' AS inferenceprovenance, + 'openaire____::openorgs' AS collectedfromid, + 'OpenOrgs Database' AS collectedfromname, + o.country || '@@@dnet:countries' AS country, + 'sysimport:crosswalk:entityregistry@@@dnet:provenance_actions' AS provenanceaction, + array_agg(DISTINCT i.otherid || '###' || i.type || '@@@dnet:pid_types') AS pid, + null AS eclegalbody, + null AS eclegalperson, + null AS ecnonprofit, + null AS ecresearchorganization, + null AS echighereducation, + null AS ecinternationalorganizationeurinterests, + null AS ecinternationalorganization, + null AS ecenterprise, + null AS ecsmevalidated, + null AS ecnutscode +FROM organizations o + LEFT OUTER JOIN acronyms a ON (a.id = o.id) + LEFT OUTER JOIN urls u ON (u.id = o.id) + LEFT OUTER JOIN other_ids i ON (i.id = o.id) + LEFT OUTER JOIN other_names n ON (n.id = o.id) +WHERE + o.status = 'approved' +GROUP BY + o.id, + o.name, + o.creation_date, + o.modification_date, + o.country; \ No newline at end of file diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/querySimilarityFromOpenOrgsDB.sql b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsSimilarityForOrgsDedup.sql similarity index 89% rename from dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/querySimilarityFromOpenOrgsDB.sql rename to dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsSimilarityForOrgsDedup.sql index 138bf6a96..e509127df 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/querySimilarityFromOpenOrgsDB.sql +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsSimilarityForOrgsDedup.sql @@ -23,7 +23,7 @@ SELECT false AS deletedbyinference, 0.99 AS trust, '' AS inferenceprovenance, - 'isSimilarTo' AS relclass + 'isSimilarTo' AS relclass FROM other_names n LEFT OUTER JOIN organizations o ON (n.id = o.id) @@ -40,8 +40,4 @@ SELECT 0.99 AS trust, '' AS inferenceprovenance, 'isDifferentFrom' AS relclass -FROM oa_duplicates WHERE reltype = 'is_different' - - ---TODO ??? ---Creare relazioni isDifferentFrom anche tra i suggerimenti: (A is_similar B) and (A is_different C) => (B is_different C) \ No newline at end of file +FROM oa_duplicates WHERE reltype = 'is_different' \ No newline at end of file diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsSimilarityForProvision.sql b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsSimilarityForProvision.sql new file mode 100644 index 000000000..db95cfe0b --- /dev/null +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsSimilarityForProvision.sql @@ -0,0 +1,12 @@ +-- relations approved by the user and suggested by the dedup +SELECT + local_id AS id1, + oa_original_id AS id2, + 'openaire____::openorgs' AS collectedfromid, + 'OpenOrgs Database' AS collectedfromname, + false AS inferred, + false AS deletedbyinference, + 0.99 AS trust, + '' AS inferenceprovenance, + 'isSimilarTo' AS relclass +FROM oa_duplicates WHERE reltype = 'is_similar' OR reltype = 'suggested'; \ No newline at end of file From 48f2b6127ea739a81f3c43be659a45dfa22bcd51 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 29 Mar 2021 14:23:18 +0200 Subject: [PATCH 184/445] [Cleaning] drop alternate identifiers with empty values --- .../dhp/schema/oaf/CleaningFunctions.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index 6c7d3e915..3be062c0c 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -152,14 +152,20 @@ public class CleaningFunctions { Optional .ofNullable(i.getPid()) .ifPresent(pid -> { - final Set pids = Sets - .newHashSet( + final Set pids = pid .stream() - .filter(p -> StringUtils.isBlank(p.getValue())) - .collect(Collectors.toList())); - final Set altIds = Sets.newHashSet(i.getAlternateIdentifier()); - i.setAlternateIdentifier(Lists.newArrayList(Sets.difference(altIds, pids))); + .filter(p -> StringUtils.isNotBlank(p.getValue())) + .collect(Collectors.toCollection(HashSet::new)); + + Optional.ofNullable(i.getAlternateIdentifier()) + .ifPresent(altId -> { + final Set altIds = altId.stream() + .filter(p -> StringUtils.isNotBlank(p.getValue())) + .collect(Collectors.toCollection(HashSet::new)); + + i.setAlternateIdentifier(Lists.newArrayList(Sets.difference(altIds, pids))); + }); }); if (Objects.isNull(i.getAccessright()) || StringUtils.isBlank(i.getAccessright().getClassid())) { From a0837ac357b9ae5d3ad5f9161f7a4826cb6b0c73 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 29 Mar 2021 15:59:58 +0200 Subject: [PATCH 185/445] [Stats update] integrating PR#100 for testing https://code-repo.d4science.org/D-Net/dnet-hadoop/pulls/100 --- 100.patch | 757 ++++++++++++++++++ .../oa/graph/stats/oozie_app/impala-shell.sh | 18 - .../scripts/computeProductionStats.sql | 8 - .../scripts/updateProductionViews.sql | 207 ----- .../stats/oozie_app/updateProductionViews.sh | 16 + .../dhp/oa/graph/stats/oozie_app/workflow.xml | 41 +- .../dhp/oa/graph/stats/oozie_app/contexts.sh | 43 + .../graph/stats/oozie_app/scripts/step10.sql | 13 - .../graph/stats/oozie_app/scripts/step2.sql | 5 +- .../graph/stats/oozie_app/scripts/step3.sql | 5 +- .../graph/stats/oozie_app/scripts/step4.sql | 5 +- .../graph/stats/oozie_app/scripts/step5.sql | 5 +- .../dhp/oa/graph/stats/oozie_app/workflow.xml | 17 + 13 files changed, 874 insertions(+), 266 deletions(-) create mode 100644 100.patch delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/impala-shell.sh delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/computeProductionStats.sql delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/updateProductionViews.sql create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/updateProductionViews.sh create mode 100644 dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh diff --git a/100.patch b/100.patch new file mode 100644 index 000000000..f28cdd0a5 --- /dev/null +++ b/100.patch @@ -0,0 +1,757 @@ +From c5fbad8093ca27deebf1b5fd5ffd39e1877c533d Mon Sep 17 00:00:00 2001 +From: antleb +Date: Thu, 4 Mar 2021 00:42:21 +0200 +Subject: [PATCH 1/8] Contexts are now downloaded instead of using the + stats_ext db + +--- + .../dhp/oa/graph/stats/oozie_app/contexts.sh | 33 +++++++++++++++++++ + .../graph/stats/oozie_app/scripts/step10.sql | 13 -------- + .../dhp/oa/graph/stats/oozie_app/workflow.xml | 17 ++++++++++ + 3 files changed, 50 insertions(+), 13 deletions(-) + create mode 100644 dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh + +diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh +new file mode 100644 +index 00000000..f06a43bb +--- /dev/null ++++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh +@@ -0,0 +1,33 @@ ++#!/usr/bin/env bash ++ ++CONTEXT_API=$1 ++TARGET_DB=$2 ++ ++TMP=/tmp/stats-update-`tr -dc A-Za-z0-9 contexts.csv ++cat contexts.csv | cut -d , -f1 | xargs -I {} curl ${CONTEXT_API}/context/{}/?all=true | /usr/local/sbin/jq -r '.[]|"\(.id|split(":")[0]),\(.id),\(.label)"' > categories.csv ++cat categories.csv | cut -d , -f2 | sed 's/:/%3A/g'| xargs -I {} curl ${CONTEXT_API}/context/category/{}/?all=true | /usr/local/sbin/jq -r '.[]|"\(.id|split("::")[0])::\(.id|split("::")[1]),\(.id),\(.label)"' > concepts.csv ++cat contexts.csv | cut -f1 -d, | sed 's/\(.*\)/\1,\1::other,other/' >> categories.csv ++cat categories.csv | cut -d, -f2 | sed 's/\(.*\)/\1,\1::other,other/' >> concepts.csv ++ ++echo "uploading context data to hdfs" ++hdfs dfs -mkdir ${TMP} ++hdfs dfs -copyFromLocal contexts.csv ${TMP} ++hdfs dfs -copyFromLocal categories.csv ${TMP} ++hdfs dfs -copyFromLocal concepts.csv ${TMP} ++hdfs dfs -chmod -R 777 ${TMP} ++ ++echo "Creating and populating impala tables" ++impala-shell -c "create table ${TARGET_DB}.context (id string, name string) row format delimited fields terminated by ',';" ++impala-shell -c "create table ${TARGET_DB}.category (context string, id string, name string) row format delimited fields terminated by ',';" ++impala-shell -c "create table ${TARGET_DB}.concept (category string, id string, name string) row format delimited fields terminated by ',';" ++impala-shell -c "load data inpath '${TMP}/contexts.csv' into table ${TARGET_DB}.context;" ++impala-shell -c "load data inpath '${TMP}/categories.csv' into table ${TARGET_DB}.category;" ++impala-shell -c "load data inpath '${TMP}/concepts.csv' into table ${TARGET_DB}.concept;" ++ ++echo "Cleaning up" ++hdfs dfs -rm -f -r -skipTrash ${TMP} ++ ++echo "Finito!" +\ No newline at end of file +diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step10.sql b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step10.sql +index 6c96317e..77fbd3b1 100644 +--- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step10.sql ++++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step10.sql +@@ -23,19 +23,6 @@ CREATE OR REPLACE VIEW ${stats_db_name}.rndexpediture AS + SELECT * + FROM ${external_stats_db_name}.rndexpediture; + +-CREATE OR REPLACE VIEW ${stats_db_name}.context AS +-SELECT * +-FROM ${external_stats_db_name}.context; +- +-CREATE OR REPLACE VIEW ${stats_db_name}.category AS +-SELECT * +-FROM ${external_stats_db_name}.category; +- +-CREATE OR REPLACE VIEW ${stats_db_name}.concept AS +-SELECT * +-FROM ${external_stats_db_name}.concept; +- +- + ------------------------------------------------------------------------------------------------ + ------------------------------------------------------------------------------------------------ + -- Creation date of the database +diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml +index 9c16f149..afb10c41 100644 +--- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml ++++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml +@@ -41,6 +41,10 @@ + hive_timeout + the time period, in seconds, after which Hive fails a transaction if a Hive client has not sent a hearbeat. The default value is 300 seconds. + ++ ++ context_api_url ++ the base url of the context api (https://services.openaire.eu/openaire) ++ + + + +@@ -263,6 +267,19 @@ + + + ++ ++ ++ ++ ${jobTracker} ++ ${nameNode} ++ contexts.sh ++ ${context_api_url} ++ ${stats_db_name} ++ contexts.sh ++ ++ ++ ++ + + + +-- +2.17.1 + + +From 6147ee495053634436abe822aaf9ba909813d8c4 Mon Sep 17 00:00:00 2001 +From: antleb +Date: Fri, 5 Mar 2021 14:12:18 +0200 +Subject: [PATCH 2/8] assigning correctly hive contexts to concepts + +--- + .../eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh | 7 +++++-- + .../dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step2.sql | 5 ++++- + .../dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step3.sql | 5 ++++- + .../dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step4.sql | 5 ++++- + .../dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step5.sql | 5 ++++- + 5 files changed, 21 insertions(+), 6 deletions(-) + +diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh +index f06a43bb..6788f88b 100644 +--- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh ++++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh +@@ -9,8 +9,8 @@ echo "Downloading context data" + curl ${CONTEXT_API}/contexts?all=true -H "accept: application/json" | /usr/local/sbin/jq -r '.[] | "\(.id),\(.label)"' > contexts.csv + cat contexts.csv | cut -d , -f1 | xargs -I {} curl ${CONTEXT_API}/context/{}/?all=true | /usr/local/sbin/jq -r '.[]|"\(.id|split(":")[0]),\(.id),\(.label)"' > categories.csv + cat categories.csv | cut -d , -f2 | sed 's/:/%3A/g'| xargs -I {} curl ${CONTEXT_API}/context/category/{}/?all=true | /usr/local/sbin/jq -r '.[]|"\(.id|split("::")[0])::\(.id|split("::")[1]),\(.id),\(.label)"' > concepts.csv +-cat contexts.csv | cut -f1 -d, | sed 's/\(.*\)/\1,\1::other,other/' >> categories.csv +-cat categories.csv | cut -d, -f2 | sed 's/\(.*\)/\1,\1::other,other/' >> concepts.csv ++cat contexts.csv | sed 's/^\(.*\),\(.*\)/\1,\1::other,\2/' >> categories.csv ++cat categories.csv | grep -v ::other | sed 's/^.*,\(.*\),\(.*\)/\1,\1::other,\2/' >> concepts.csv + + echo "uploading context data to hdfs" + hdfs dfs -mkdir ${TMP} +@@ -29,5 +29,8 @@ impala-shell -c "load data inpath '${TMP}/concepts.csv' into table ${TARGET_DB}. + + echo "Cleaning up" + hdfs dfs -rm -f -r -skipTrash ${TMP} ++rm concepts.csv ++rm categories.csv ++rm contexts.csv + + echo "Finito!" +\ No newline at end of file +diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step2.sql b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step2.sql +index 62a15856..75b24b18 100644 +--- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step2.sql ++++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step2.sql +@@ -47,7 +47,10 @@ from ${openaire_db_name}.publication p + where p.datainfo.deletedbyinference = false; + + CREATE TABLE ${stats_db_name}.publication_concepts AS +-SELECT substr(p.id, 4) as id, contexts.context.id as concept ++SELECT substr(p.id, 4) as id, case ++ when contexts.context.id RLIKE '^[^::]+::[^::]+::.+$' then contexts.context.id ++ when contexts.context.id RLIKE '^[^::]+::[^::]+$' then concat(contexts.context.id, '::other') ++ when contexts.context.id RLIKE '^[^::]+$' then concat(contexts.context.id, '::other::other') END as concept + from ${openaire_db_name}.publication p + LATERAL VIEW explode(p.context) contexts as context + where p.datainfo.deletedbyinference = false; +diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step3.sql b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step3.sql +index dcd5ad85..540cc03a 100644 +--- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step3.sql ++++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step3.sql +@@ -54,7 +54,10 @@ FROM ${openaire_db_name}.dataset p + where p.datainfo.deletedbyinference = false; + + CREATE TABLE ${stats_db_name}.dataset_concepts AS +-SELECT substr(p.id, 4) as id, contexts.context.id as concept ++SELECT substr(p.id, 4) as id, case ++ when contexts.context.id RLIKE '^[^::]+::[^::]+::.+$' then contexts.context.id ++ when contexts.context.id RLIKE '^[^::]+::[^::]+$' then concat(contexts.context.id, '::other') ++ when contexts.context.id RLIKE '^[^::]+$' then concat(contexts.context.id, '::other::other') END as concept + from ${openaire_db_name}.dataset p + LATERAL VIEW explode(p.context) contexts as context + where p.datainfo.deletedbyinference = false; +diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step4.sql b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step4.sql +index fd5390e6..54345e07 100644 +--- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step4.sql ++++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step4.sql +@@ -54,7 +54,10 @@ FROM ${openaire_db_name}.software p + where p.datainfo.deletedbyinference = false; + + CREATE TABLE ${stats_db_name}.software_concepts AS +-SELECT substr(p.id, 4) AS id, contexts.context.id AS concept ++SELECT substr(p.id, 4) as id, case ++ when contexts.context.id RLIKE '^[^::]+::[^::]+::.+$' then contexts.context.id ++ when contexts.context.id RLIKE '^[^::]+::[^::]+$' then concat(contexts.context.id, '::other') ++ when contexts.context.id RLIKE '^[^::]+$' then concat(contexts.context.id, '::other::other') END as concept + FROM ${openaire_db_name}.software p + LATERAL VIEW explode(p.context) contexts AS context + where p.datainfo.deletedbyinference = false; +diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step5.sql b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step5.sql +index b359b596..36ad5d92 100644 +--- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step5.sql ++++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step5.sql +@@ -52,7 +52,10 @@ FROM ${openaire_db_name}.otherresearchproduct p LATERAL VIEW explode(p.instance. + where p.datainfo.deletedbyinference = false; + + CREATE TABLE ${stats_db_name}.otherresearchproduct_concepts AS +-SELECT substr(p.id, 4) AS id, contexts.context.id AS concept ++SELECT substr(p.id, 4) as id, case ++ when contexts.context.id RLIKE '^[^::]+::[^::]+::.+$' then contexts.context.id ++ when contexts.context.id RLIKE '^[^::]+::[^::]+$' then concat(contexts.context.id, '::other') ++ when contexts.context.id RLIKE '^[^::]+$' then concat(contexts.context.id, '::other::other') END as concept + FROM ${openaire_db_name}.otherresearchproduct p LATERAL VIEW explode(p.context) contexts AS context + where p.datainfo.deletedbyinference = false; + +-- +2.17.1 + + +From f40c150a0d549e2dbcfd42ecf81e17ad4b505391 Mon Sep 17 00:00:00 2001 +From: antleb +Date: Sat, 6 Mar 2021 00:35:57 +0200 +Subject: [PATCH 3/8] fixed steps... + +--- + .../eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml +index afb10c41..2184cb8a 100644 +--- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml ++++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml +@@ -264,7 +264,7 @@ + stats_db_name=${stats_db_name} + openaire_db_name=${openaire_db_name} + +- ++ + + + +@@ -277,7 +277,7 @@ + ${stats_db_name} + contexts.sh + +- ++ + + + +-- +2.17.1 + + +From fa1ec5b5e9b6038b3b565422af5c6406f21220d3 Mon Sep 17 00:00:00 2001 +From: antleb +Date: Wed, 10 Mar 2021 14:05:58 +0200 +Subject: [PATCH 4/8] fixed typo... + +--- + .../eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml +index 2184cb8a..321500e2 100644 +--- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml ++++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml +@@ -277,7 +277,7 @@ + ${stats_db_name} + contexts.sh + +- ++ + + + +-- +2.17.1 + + +From 3c75a050443942b632cf8469b5af16a8c61e7569 Mon Sep 17 00:00:00 2001 +From: antleb +Date: Fri, 12 Mar 2021 13:47:04 +0200 +Subject: [PATCH 5/8] fixed a ton of typos + +--- + .../scripts/computeProductionStats.sql | 8 ------- + .../stats/oozie_app/updateProductionViews.sh | 18 ++++++++++++++++ + .../dhp/oa/graph/stats/oozie_app/contexts.sh | 21 ++++++++++++------- + 3 files changed, 32 insertions(+), 15 deletions(-) + delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/computeProductionStats.sql + create mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/updateProductionViews.sh + +diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/computeProductionStats.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/computeProductionStats.sql +deleted file mode 100644 +index 34e48a18..00000000 +--- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/computeProductionStats.sql ++++ /dev/null +@@ -1,8 +0,0 @@ +------------------------------------------------------- +------------------------------------------------------- +--- Impala table statistics - Needed to make the tables +--- visible for impala +------------------------------------------------------- +------------------------------------------------------- +- +-INVALIDATE METADATA ${stats_db_name}; +diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/updateProductionViews.sh b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/updateProductionViews.sh +new file mode 100644 +index 00000000..57acb2ee +--- /dev/null ++++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/updateProductionViews.sh +@@ -0,0 +1,18 @@ ++export PYTHON_EGG_CACHE=/home/$(whoami)/.python-eggs ++export link_folder=/tmp/impala-shell-python-egg-cache-$(whoami) ++if ! [ -L $link_folder ] ++then ++ rm -Rf "$link_folder" ++ ln -sfn ${PYTHON_EGG_CACHE}${link_folder} ${link_folder} ++fi ++ ++export SOURCE=$1 ++export SHADOW=$2 ++ ++echo "Updating shadow database" ++impala-shell -d ${SOURCE} -q "invalidate metadata" ++impala-shell -d ${SOURCE} -q "show tables" --delimited | sed "s/^\(.*\)/compute stats ${SOURCE}.\1;/" | impala-shell -c -f - ++impala-shell -q "create database if not exists ${SHADOW}" ++impala-shell -d ${SHADOW} -q "show tables" --delimited | sed "s/^/drop view if exists ${SHADOW}./" | sed "s/$/;/" | impala-shell -c -f - ++impala-shell -d ${SOURCE} -q "show tables" --delimited | sed "s/\(.*\)/create view ${SHADOW}.\1 as select * from ${SOURCE}.\1;/" | impala-shell -c -f - ++echo "Shadow db ready!" +\ No newline at end of file +diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh +index 6788f88b..c28be50d 100644 +--- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh ++++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh +@@ -1,4 +1,10 @@ +-#!/usr/bin/env bash ++export PYTHON_EGG_CACHE=/home/$(whoami)/.python-eggs ++export link_folder=/tmp/impala-shell-python-egg-cache-$(whoami) ++if ! [ -L $link_folder ] ++then ++ rm -Rf "$link_folder" ++ ln -sfn ${PYTHON_EGG_CACHE}${link_folder} ${link_folder} ++fi + + CONTEXT_API=$1 + TARGET_DB=$2 +@@ -20,12 +26,13 @@ hdfs dfs -copyFromLocal concepts.csv ${TMP} + hdfs dfs -chmod -R 777 ${TMP} + + echo "Creating and populating impala tables" +-impala-shell -c "create table ${TARGET_DB}.context (id string, name string) row format delimited fields terminated by ',';" +-impala-shell -c "create table ${TARGET_DB}.category (context string, id string, name string) row format delimited fields terminated by ',';" +-impala-shell -c "create table ${TARGET_DB}.concept (category string, id string, name string) row format delimited fields terminated by ',';" +-impala-shell -c "load data inpath '${TMP}/contexts.csv' into table ${TARGET_DB}.context;" +-impala-shell -c "load data inpath '${TMP}/categories.csv' into table ${TARGET_DB}.category;" +-impala-shell -c "load data inpath '${TMP}/concepts.csv' into table ${TARGET_DB}.concept;" ++impala-shell -q "create table ${TARGET_DB}.context (id string, name string) row format delimited fields terminated by ','" ++impala-shell -q "create table ${TARGET_DB}.category (context string, id string, name string) row format delimited fields terminated by ','" ++impala-shell -q "create table ${TARGET_DB}.concept (category string, id string, name string) row format delimited fields terminated by ','" ++impala-shell -d ${TARGET_DB} -q "invalidate metadata" ++impala-shell -q "load data inpath '${TMP}/contexts.csv' into table ${TARGET_DB}.context" ++impala-shell -q "load data inpath '${TMP}/categories.csv' into table ${TARGET_DB}.category" ++impala-shell -q "load data inpath '${TMP}/concepts.csv' into table ${TARGET_DB}.concept" + + echo "Cleaning up" + hdfs dfs -rm -f -r -skipTrash ${TMP} +-- +2.17.1 + + +From 236435b47010ea1ab94c3f018dcf278f5d2c44aa Mon Sep 17 00:00:00 2001 +From: antleb +Date: Fri, 12 Mar 2021 14:11:21 +0200 +Subject: [PATCH 6/8] following redirects + +--- + .../eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh +index c28be50d..29b225e3 100644 +--- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh ++++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh +@@ -12,9 +12,9 @@ TARGET_DB=$2 + TMP=/tmp/stats-update-`tr -dc A-Za-z0-9 contexts.csv +-cat contexts.csv | cut -d , -f1 | xargs -I {} curl ${CONTEXT_API}/context/{}/?all=true | /usr/local/sbin/jq -r '.[]|"\(.id|split(":")[0]),\(.id),\(.label)"' > categories.csv +-cat categories.csv | cut -d , -f2 | sed 's/:/%3A/g'| xargs -I {} curl ${CONTEXT_API}/context/category/{}/?all=true | /usr/local/sbin/jq -r '.[]|"\(.id|split("::")[0])::\(.id|split("::")[1]),\(.id),\(.label)"' > concepts.csv ++curl -L ${CONTEXT_API}/contexts?all=true -H "accept: application/json" | /usr/local/sbin/jq -r '.[] | "\(.id),\(.label)"' > contexts.csv ++cat contexts.csv | cut -d , -f1 | xargs -I {} curl -L ${CONTEXT_API}/context/{}/?all=true | /usr/local/sbin/jq -r '.[]|"\(.id|split(":")[0]),\(.id),\(.label)"' > categories.csv ++cat categories.csv | cut -d , -f2 | sed 's/:/%3A/g'| xargs -I {} curl -L ${CONTEXT_API}/context/category/{}/?all=true | /usr/local/sbin/jq -r '.[]|"\(.id|split("::")[0])::\(.id|split("::")[1]),\(.id),\(.label)"' > concepts.csv + cat contexts.csv | sed 's/^\(.*\),\(.*\)/\1,\1::other,\2/' >> categories.csv + cat categories.csv | grep -v ::other | sed 's/^.*,\(.*\),\(.*\)/\1,\1::other,\2/' >> concepts.csv + +-- +2.17.1 + + +From 60ebdf2dbe704733809f401df70bffcf49cede29 Mon Sep 17 00:00:00 2001 +From: antleb +Date: Fri, 12 Mar 2021 16:34:53 +0200 +Subject: [PATCH 7/8] update promote wf to support monitor&production + +--- + .../oa/graph/stats/oozie_app/impala-shell.sh | 18 -- + .../scripts/updateProductionViews.sql | 207 ------------------ + 2 files changed, 225 deletions(-) + delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/impala-shell.sh + delete mode 100644 dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/updateProductionViews.sql + +diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/impala-shell.sh b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/impala-shell.sh +deleted file mode 100644 +index 70112dc7..00000000 +--- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/impala-shell.sh ++++ /dev/null +@@ -1,18 +0,0 @@ +-export PYTHON_EGG_CACHE=/home/$(whoami)/.python-eggs +-export link_folder=/tmp/impala-shell-python-egg-cache-$(whoami) +-if ! [ -L $link_folder ] +-then +- rm -Rf "$link_folder" +- ln -sfn ${PYTHON_EGG_CACHE}${link_folder} ${link_folder} +-fi +- +-echo "Getting file from " $3 +-hdfs dfs -copyToLocal $3 +- +-echo "Running impala shell make the new database visible" +-impala-shell -q "INVALIDATE METADATA;" +- +-echo "Running impala shell to compute new table stats" +-impala-shell -d $1 -f $2 +-echo "Impala shell finished" +-rm $2 +diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/updateProductionViews.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/updateProductionViews.sql +deleted file mode 100644 +index 48f8d58f..00000000 +--- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/updateProductionViews.sql ++++ /dev/null +@@ -1,207 +0,0 @@ +------------------------------------------------------- +------------------------------------------------------- +--- Shadow schema table exchange +------------------------------------------------------- +------------------------------------------------------- +- +--- Dropping old views +-DROP VIEW IF EXISTS ${stats_db_production_name}.category; +-DROP VIEW IF EXISTS ${stats_db_production_name}.concept; +-DROP VIEW IF EXISTS ${stats_db_production_name}.context; +-DROP VIEW IF EXISTS ${stats_db_production_name}.country; +-DROP VIEW IF EXISTS ${stats_db_production_name}.countrygdp; +-DROP VIEW IF EXISTS ${stats_db_production_name}.creation_date; +-DROP VIEW IF EXISTS ${stats_db_production_name}.dataset; +-DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_citations; +-DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_classifications; +-DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_concepts; +-DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_datasources; +-DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_languages; +-DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_licenses; +-DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_oids; +-DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_pids; +-DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_refereed; +-DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_sources; +-DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_topics; +-DROP VIEW IF EXISTS ${stats_db_production_name}.datasource; +-DROP VIEW IF EXISTS ${stats_db_production_name}.datasource_languages; +-DROP VIEW IF EXISTS ${stats_db_production_name}.datasource_oids; +-DROP VIEW IF EXISTS ${stats_db_production_name}.datasource_organizations; +-DROP VIEW IF EXISTS ${stats_db_production_name}.datasource_results; +-DROP VIEW IF EXISTS ${stats_db_production_name}.datasource_sources; +-DROP VIEW IF EXISTS ${stats_db_production_name}.funder; +-DROP VIEW IF EXISTS ${stats_db_production_name}.fundref; +-DROP VIEW IF EXISTS ${stats_db_production_name}.numbers_country; +-DROP VIEW IF EXISTS ${stats_db_production_name}.organization; +-DROP VIEW IF EXISTS ${stats_db_production_name}.organization_datasources; +-DROP VIEW IF EXISTS ${stats_db_production_name}.organization_pids; +-DROP VIEW IF EXISTS ${stats_db_production_name}.organization_projects; +-DROP VIEW IF EXISTS ${stats_db_production_name}.organization_sources; +-DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct; +-DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_citations; +-DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_classifications; +-DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_concepts; +-DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_datasources; +-DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_languages; +-DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_licenses; +-DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_oids; +-DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_pids; +-DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_refereed; +-DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_sources; +-DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_topics; +-DROP VIEW IF EXISTS ${stats_db_production_name}.project; +-DROP VIEW IF EXISTS ${stats_db_production_name}.project_oids; +-DROP VIEW IF EXISTS ${stats_db_production_name}.project_organizations; +-DROP VIEW IF EXISTS ${stats_db_production_name}.project_results; +-DROP VIEW IF EXISTS ${stats_db_production_name}.project_resultcount; +-DROP VIEW IF EXISTS ${stats_db_production_name}.project_results_publication; +-DROP VIEW IF EXISTS ${stats_db_production_name}.publication; +-DROP VIEW IF EXISTS ${stats_db_production_name}.publication_citations; +-DROP VIEW IF EXISTS ${stats_db_production_name}.publication_classifications; +-DROP VIEW IF EXISTS ${stats_db_production_name}.publication_concepts; +-DROP VIEW IF EXISTS ${stats_db_production_name}.publication_datasources; +-DROP VIEW IF EXISTS ${stats_db_production_name}.publication_languages; +-DROP VIEW IF EXISTS ${stats_db_production_name}.publication_licenses; +-DROP VIEW IF EXISTS ${stats_db_production_name}.publication_oids; +-DROP VIEW IF EXISTS ${stats_db_production_name}.publication_pids; +-DROP VIEW IF EXISTS ${stats_db_production_name}.publication_refereed; +-DROP VIEW IF EXISTS ${stats_db_production_name}.publication_sources; +-DROP VIEW IF EXISTS ${stats_db_production_name}.publication_topics; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_affiliated_country; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_citations; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_classifications; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_concepts; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_datasources; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_deposited_country; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_fundercount; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_gold; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_greenoa; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_languages; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_licenses; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_oids; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_organization; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_peerreviewed; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_pids; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_projectcount; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_projects; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_refereed; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_sources; +-DROP VIEW IF EXISTS ${stats_db_production_name}.result_topics; +-DROP VIEW IF EXISTS ${stats_db_production_name}.rndexpediture; +-DROP VIEW IF EXISTS ${stats_db_production_name}.roarmap; +-DROP VIEW IF EXISTS ${stats_db_production_name}.software; +-DROP VIEW IF EXISTS ${stats_db_production_name}.software_citations; +-DROP VIEW IF EXISTS ${stats_db_production_name}.software_classifications; +-DROP VIEW IF EXISTS ${stats_db_production_name}.software_concepts; +-DROP VIEW IF EXISTS ${stats_db_production_name}.software_datasources; +-DROP VIEW IF EXISTS ${stats_db_production_name}.software_languages; +-DROP VIEW IF EXISTS ${stats_db_production_name}.software_licenses; +-DROP VIEW IF EXISTS ${stats_db_production_name}.software_oids; +-DROP VIEW IF EXISTS ${stats_db_production_name}.software_pids; +-DROP VIEW IF EXISTS ${stats_db_production_name}.software_refereed; +-DROP VIEW IF EXISTS ${stats_db_production_name}.software_sources; +-DROP VIEW IF EXISTS ${stats_db_production_name}.software_topics; +- +- +--- Creating the shadow database, in case it doesn't exist +-CREATE database IF NOT EXISTS ${stats_db_production_name}; +- +--- Creating new views +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.category AS SELECT * FROM ${stats_db_name}.category; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.concept AS SELECT * FROM ${stats_db_name}.concept; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.context AS SELECT * FROM ${stats_db_name}.context; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.country AS SELECT * FROM ${stats_db_name}.country; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.countrygdp AS SELECT * FROM ${stats_db_name}.countrygdp; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.creation_date AS SELECT * FROM ${stats_db_name}.creation_date; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset AS SELECT * FROM ${stats_db_name}.dataset; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_citations AS SELECT * FROM ${stats_db_name}.dataset_citations; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_classifications AS SELECT * FROM ${stats_db_name}.dataset_classifications; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_concepts AS SELECT * FROM ${stats_db_name}.dataset_concepts; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_datasources AS SELECT * FROM ${stats_db_name}.dataset_datasources; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_languages AS SELECT * FROM ${stats_db_name}.dataset_languages; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_licenses AS SELECT * FROM ${stats_db_name}.dataset_licenses; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_oids AS SELECT * FROM ${stats_db_name}.dataset_oids; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_pids AS SELECT * FROM ${stats_db_name}.dataset_pids; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_refereed AS SELECT * FROM ${stats_db_name}.dataset_refereed; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_sources AS SELECT * FROM ${stats_db_name}.dataset_sources; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_topics AS SELECT * FROM ${stats_db_name}.dataset_topics; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.datasource AS SELECT * FROM ${stats_db_name}.datasource; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.datasource_languages AS SELECT * FROM ${stats_db_name}.datasource_languages; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.datasource_oids AS SELECT * FROM ${stats_db_name}.datasource_oids; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.datasource_organizations AS SELECT * FROM ${stats_db_name}.datasource_organizations; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.datasource_results AS SELECT * FROM ${stats_db_name}.datasource_results; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.datasource_sources AS SELECT * FROM ${stats_db_name}.datasource_sources; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.funder AS SELECT * FROM ${stats_db_name}.funder; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.fundref AS SELECT * FROM ${stats_db_name}.fundref; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.numbers_country AS SELECT * FROM ${stats_db_name}.numbers_country; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.organization AS SELECT * FROM ${stats_db_name}.organization; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.organization_datasources AS SELECT * FROM ${stats_db_name}.organization_datasources; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.organization_pids AS SELECT * FROM ${stats_db_name}.organization_pids; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.organization_projects AS SELECT * FROM ${stats_db_name}.organization_projects; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.organization_sources AS SELECT * FROM ${stats_db_name}.organization_sources; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct AS SELECT * FROM ${stats_db_name}.otherresearchproduct; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_citations AS SELECT * FROM ${stats_db_name}.otherresearchproduct_citations; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_classifications AS SELECT * FROM ${stats_db_name}.otherresearchproduct_classifications; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_concepts AS SELECT * FROM ${stats_db_name}.otherresearchproduct_concepts; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_datasources AS SELECT * FROM ${stats_db_name}.otherresearchproduct_datasources; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_languages AS SELECT * FROM ${stats_db_name}.otherresearchproduct_languages; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_licenses AS SELECT * FROM ${stats_db_name}.otherresearchproduct_licenses; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_oids AS SELECT * FROM ${stats_db_name}.otherresearchproduct_oids; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_pids AS SELECT * FROM ${stats_db_name}.otherresearchproduct_pids; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_refereed AS SELECT * FROM ${stats_db_name}.otherresearchproduct_refereed; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_sources AS SELECT * FROM ${stats_db_name}.otherresearchproduct_sources; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_topics AS SELECT * FROM ${stats_db_name}.otherresearchproduct_topics; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.project AS SELECT * FROM ${stats_db_name}.project; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.project_oids AS SELECT * FROM ${stats_db_name}.project_oids; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.project_organizations AS SELECT * FROM ${stats_db_name}.project_organizations; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.project_results AS SELECT * FROM ${stats_db_name}.project_results; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.project_resultcount AS SELECT * FROM ${stats_db_name}.project_resultcount; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.project_results_publication AS SELECT * FROM ${stats_db_name}.project_results_publication; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication AS SELECT * FROM ${stats_db_name}.publication; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_citations AS SELECT * FROM ${stats_db_name}.publication_citations; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_classifications AS SELECT * FROM ${stats_db_name}.publication_classifications; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_concepts AS SELECT * FROM ${stats_db_name}.publication_concepts; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_datasources AS SELECT * FROM ${stats_db_name}.publication_datasources; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_languages AS SELECT * FROM ${stats_db_name}.publication_languages; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_licenses AS SELECT * FROM ${stats_db_name}.publication_licenses; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_oids AS SELECT * FROM ${stats_db_name}.publication_oids; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_pids AS SELECT * FROM ${stats_db_name}.publication_pids; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_refereed AS SELECT * FROM ${stats_db_name}.publication_refereed; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_sources AS SELECT * FROM ${stats_db_name}.publication_sources; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_topics AS SELECT * FROM ${stats_db_name}.publication_topics; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result AS SELECT * FROM ${stats_db_name}.result; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_affiliated_country AS SELECT * FROM ${stats_db_name}.result_affiliated_country; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_citations AS SELECT * FROM ${stats_db_name}.result_citations; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_classifications AS SELECT * FROM ${stats_db_name}.result_classifications; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_concepts AS SELECT * FROM ${stats_db_name}.result_concepts; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_datasources AS SELECT * FROM ${stats_db_name}.result_datasources; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_deposited_country AS SELECT * FROM ${stats_db_name}.result_deposited_country; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_fundercount AS SELECT * FROM ${stats_db_name}.result_fundercount; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_gold AS SELECT * FROM ${stats_db_name}.result_gold; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_greenoa AS SELECT * FROM ${stats_db_name}.result_greenoa; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_languages AS SELECT * FROM ${stats_db_name}.result_languages; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_licenses AS SELECT * FROM ${stats_db_name}.result_licenses; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_oids AS SELECT * FROM ${stats_db_name}.result_oids; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_organization AS SELECT * FROM ${stats_db_name}.result_organization; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_peerreviewed AS SELECT * FROM ${stats_db_name}.result_peerreviewed; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_pids AS SELECT * FROM ${stats_db_name}.result_pids; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_projectcount AS SELECT * FROM ${stats_db_name}.result_projectcount; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_projects AS SELECT * FROM ${stats_db_name}.result_projects; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_refereed AS SELECT * FROM ${stats_db_name}.result_refereed; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_sources AS SELECT * FROM ${stats_db_name}.result_sources; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_topics AS SELECT * FROM ${stats_db_name}.result_topics; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.rndexpediture AS SELECT * FROM ${stats_db_name}.rndexpediture; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.roarmap AS SELECT * FROM ${stats_db_name}.roarmap; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software AS SELECT * FROM ${stats_db_name}.software; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_citations AS SELECT * FROM ${stats_db_name}.software_citations; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_classifications AS SELECT * FROM ${stats_db_name}.software_classifications; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_concepts AS SELECT * FROM ${stats_db_name}.software_concepts; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_datasources AS SELECT * FROM ${stats_db_name}.software_datasources; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_languages AS SELECT * FROM ${stats_db_name}.software_languages; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_licenses AS SELECT * FROM ${stats_db_name}.software_licenses; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_oids AS SELECT * FROM ${stats_db_name}.software_oids; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_pids AS SELECT * FROM ${stats_db_name}.software_pids; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_refereed AS SELECT * FROM ${stats_db_name}.software_refereed; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_sources AS SELECT * FROM ${stats_db_name}.software_sources; +-CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_topics AS SELECT * FROM ${stats_db_name}.software_topics; +-- +2.17.1 + + +From 0ba0a6b9dac25f5ec73e8eafefbf7f91442ad1c5 Mon Sep 17 00:00:00 2001 +From: antleb +Date: Fri, 12 Mar 2021 16:42:59 +0200 +Subject: [PATCH 8/8] update promote wf to support monitor&production + +--- + .../stats/oozie_app/updateProductionViews.sh | 14 +++---- + .../dhp/oa/graph/stats/oozie_app/workflow.xml | 37 ++++++++++++------- + 2 files changed, 29 insertions(+), 22 deletions(-) + +diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/updateProductionViews.sh b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/updateProductionViews.sh +index 57acb2ee..3e510e87 100644 +--- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/updateProductionViews.sh ++++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/updateProductionViews.sh +@@ -7,12 +7,10 @@ then + fi + + export SOURCE=$1 +-export SHADOW=$2 ++export PRODUCTION=$2 + +-echo "Updating shadow database" +-impala-shell -d ${SOURCE} -q "invalidate metadata" +-impala-shell -d ${SOURCE} -q "show tables" --delimited | sed "s/^\(.*\)/compute stats ${SOURCE}.\1;/" | impala-shell -c -f - +-impala-shell -q "create database if not exists ${SHADOW}" +-impala-shell -d ${SHADOW} -q "show tables" --delimited | sed "s/^/drop view if exists ${SHADOW}./" | sed "s/$/;/" | impala-shell -c -f - +-impala-shell -d ${SOURCE} -q "show tables" --delimited | sed "s/\(.*\)/create view ${SHADOW}.\1 as select * from ${SOURCE}.\1;/" | impala-shell -c -f - +-echo "Shadow db ready!" +\ No newline at end of file ++echo "Updating ${PRODUCTION} database" ++impala-shell -q "create database if not exists ${PRODUCTION}" ++impala-shell -d ${PRODUCTION} -q "show tables" --delimited | sed "s/^/drop view if exists ${PRODUCTION}./" | sed "s/$/;/" | impala-shell -c -f - ++impala-shell -d ${SOURCE} -q "show tables" --delimited | sed "s/\(.*\)/create view ${PRODUCTION}.\1 as select * from ${SOURCE}.\1;/" | impala-shell -c -f - ++echo "Production db ready!" +\ No newline at end of file +diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml +index d744f18d..0d8ff7ee 100644 +--- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml ++++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml +@@ -6,7 +6,15 @@ + + + stats_db_production_name +- the name of the production schema ++ the name of the public production schema ++ ++ ++ monitor_db_name ++ the monitor database name ++ ++ ++ monitor_db_production_name ++ the name of the monitor public database + + + stats_tool_api_url +@@ -48,25 +56,26 @@ + + + +- +- ${hive_jdbc_url} +- +- stats_db_name=${stats_db_name} +- stats_db_production_name=${stats_db_production_name} +- +- ++ ++ ${jobTracker} ++ ${nameNode} ++ updateProductionViews.sh ++ ${stats_db_name} ++ ${stats_db_production_name} ++ updateProductionViews.sh ++ ++ + + + +- ++ + + ${jobTracker} + ${nameNode} +- impala-shell.sh +- ${stats_db_production_name} +- computeProductionStats.sql +- ${wf:appPath()}/scripts/computeProductionStats.sql +- impala-shell.sh ++ updateProductionViews.sh ++ ${monitor_db_name} ++ ${monitor_db_production_name} ++ updateProductionViews.sh + + + +-- +2.17.1 + diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/impala-shell.sh b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/impala-shell.sh deleted file mode 100644 index 70112dc7b..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/impala-shell.sh +++ /dev/null @@ -1,18 +0,0 @@ -export PYTHON_EGG_CACHE=/home/$(whoami)/.python-eggs -export link_folder=/tmp/impala-shell-python-egg-cache-$(whoami) -if ! [ -L $link_folder ] -then - rm -Rf "$link_folder" - ln -sfn ${PYTHON_EGG_CACHE}${link_folder} ${link_folder} -fi - -echo "Getting file from " $3 -hdfs dfs -copyToLocal $3 - -echo "Running impala shell make the new database visible" -impala-shell -q "INVALIDATE METADATA;" - -echo "Running impala shell to compute new table stats" -impala-shell -d $1 -f $2 -echo "Impala shell finished" -rm $2 diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/computeProductionStats.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/computeProductionStats.sql deleted file mode 100644 index 34e48a18a..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/computeProductionStats.sql +++ /dev/null @@ -1,8 +0,0 @@ ------------------------------------------------------- ------------------------------------------------------- --- Impala table statistics - Needed to make the tables --- visible for impala ------------------------------------------------------- ------------------------------------------------------- - -INVALIDATE METADATA ${stats_db_name}; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/updateProductionViews.sql b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/updateProductionViews.sql deleted file mode 100644 index 48f8d58fd..000000000 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/updateProductionViews.sql +++ /dev/null @@ -1,207 +0,0 @@ ------------------------------------------------------- ------------------------------------------------------- --- Shadow schema table exchange ------------------------------------------------------- ------------------------------------------------------- - --- Dropping old views -DROP VIEW IF EXISTS ${stats_db_production_name}.category; -DROP VIEW IF EXISTS ${stats_db_production_name}.concept; -DROP VIEW IF EXISTS ${stats_db_production_name}.context; -DROP VIEW IF EXISTS ${stats_db_production_name}.country; -DROP VIEW IF EXISTS ${stats_db_production_name}.countrygdp; -DROP VIEW IF EXISTS ${stats_db_production_name}.creation_date; -DROP VIEW IF EXISTS ${stats_db_production_name}.dataset; -DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_citations; -DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_classifications; -DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_concepts; -DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_datasources; -DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_languages; -DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_licenses; -DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_oids; -DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_pids; -DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_refereed; -DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_sources; -DROP VIEW IF EXISTS ${stats_db_production_name}.dataset_topics; -DROP VIEW IF EXISTS ${stats_db_production_name}.datasource; -DROP VIEW IF EXISTS ${stats_db_production_name}.datasource_languages; -DROP VIEW IF EXISTS ${stats_db_production_name}.datasource_oids; -DROP VIEW IF EXISTS ${stats_db_production_name}.datasource_organizations; -DROP VIEW IF EXISTS ${stats_db_production_name}.datasource_results; -DROP VIEW IF EXISTS ${stats_db_production_name}.datasource_sources; -DROP VIEW IF EXISTS ${stats_db_production_name}.funder; -DROP VIEW IF EXISTS ${stats_db_production_name}.fundref; -DROP VIEW IF EXISTS ${stats_db_production_name}.numbers_country; -DROP VIEW IF EXISTS ${stats_db_production_name}.organization; -DROP VIEW IF EXISTS ${stats_db_production_name}.organization_datasources; -DROP VIEW IF EXISTS ${stats_db_production_name}.organization_pids; -DROP VIEW IF EXISTS ${stats_db_production_name}.organization_projects; -DROP VIEW IF EXISTS ${stats_db_production_name}.organization_sources; -DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct; -DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_citations; -DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_classifications; -DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_concepts; -DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_datasources; -DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_languages; -DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_licenses; -DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_oids; -DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_pids; -DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_refereed; -DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_sources; -DROP VIEW IF EXISTS ${stats_db_production_name}.otherresearchproduct_topics; -DROP VIEW IF EXISTS ${stats_db_production_name}.project; -DROP VIEW IF EXISTS ${stats_db_production_name}.project_oids; -DROP VIEW IF EXISTS ${stats_db_production_name}.project_organizations; -DROP VIEW IF EXISTS ${stats_db_production_name}.project_results; -DROP VIEW IF EXISTS ${stats_db_production_name}.project_resultcount; -DROP VIEW IF EXISTS ${stats_db_production_name}.project_results_publication; -DROP VIEW IF EXISTS ${stats_db_production_name}.publication; -DROP VIEW IF EXISTS ${stats_db_production_name}.publication_citations; -DROP VIEW IF EXISTS ${stats_db_production_name}.publication_classifications; -DROP VIEW IF EXISTS ${stats_db_production_name}.publication_concepts; -DROP VIEW IF EXISTS ${stats_db_production_name}.publication_datasources; -DROP VIEW IF EXISTS ${stats_db_production_name}.publication_languages; -DROP VIEW IF EXISTS ${stats_db_production_name}.publication_licenses; -DROP VIEW IF EXISTS ${stats_db_production_name}.publication_oids; -DROP VIEW IF EXISTS ${stats_db_production_name}.publication_pids; -DROP VIEW IF EXISTS ${stats_db_production_name}.publication_refereed; -DROP VIEW IF EXISTS ${stats_db_production_name}.publication_sources; -DROP VIEW IF EXISTS ${stats_db_production_name}.publication_topics; -DROP VIEW IF EXISTS ${stats_db_production_name}.result; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_affiliated_country; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_citations; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_classifications; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_concepts; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_datasources; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_deposited_country; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_fundercount; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_gold; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_greenoa; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_languages; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_licenses; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_oids; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_organization; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_peerreviewed; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_pids; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_projectcount; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_projects; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_refereed; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_sources; -DROP VIEW IF EXISTS ${stats_db_production_name}.result_topics; -DROP VIEW IF EXISTS ${stats_db_production_name}.rndexpediture; -DROP VIEW IF EXISTS ${stats_db_production_name}.roarmap; -DROP VIEW IF EXISTS ${stats_db_production_name}.software; -DROP VIEW IF EXISTS ${stats_db_production_name}.software_citations; -DROP VIEW IF EXISTS ${stats_db_production_name}.software_classifications; -DROP VIEW IF EXISTS ${stats_db_production_name}.software_concepts; -DROP VIEW IF EXISTS ${stats_db_production_name}.software_datasources; -DROP VIEW IF EXISTS ${stats_db_production_name}.software_languages; -DROP VIEW IF EXISTS ${stats_db_production_name}.software_licenses; -DROP VIEW IF EXISTS ${stats_db_production_name}.software_oids; -DROP VIEW IF EXISTS ${stats_db_production_name}.software_pids; -DROP VIEW IF EXISTS ${stats_db_production_name}.software_refereed; -DROP VIEW IF EXISTS ${stats_db_production_name}.software_sources; -DROP VIEW IF EXISTS ${stats_db_production_name}.software_topics; - - --- Creating the shadow database, in case it doesn't exist -CREATE database IF NOT EXISTS ${stats_db_production_name}; - --- Creating new views -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.category AS SELECT * FROM ${stats_db_name}.category; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.concept AS SELECT * FROM ${stats_db_name}.concept; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.context AS SELECT * FROM ${stats_db_name}.context; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.country AS SELECT * FROM ${stats_db_name}.country; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.countrygdp AS SELECT * FROM ${stats_db_name}.countrygdp; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.creation_date AS SELECT * FROM ${stats_db_name}.creation_date; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset AS SELECT * FROM ${stats_db_name}.dataset; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_citations AS SELECT * FROM ${stats_db_name}.dataset_citations; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_classifications AS SELECT * FROM ${stats_db_name}.dataset_classifications; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_concepts AS SELECT * FROM ${stats_db_name}.dataset_concepts; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_datasources AS SELECT * FROM ${stats_db_name}.dataset_datasources; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_languages AS SELECT * FROM ${stats_db_name}.dataset_languages; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_licenses AS SELECT * FROM ${stats_db_name}.dataset_licenses; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_oids AS SELECT * FROM ${stats_db_name}.dataset_oids; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_pids AS SELECT * FROM ${stats_db_name}.dataset_pids; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_refereed AS SELECT * FROM ${stats_db_name}.dataset_refereed; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_sources AS SELECT * FROM ${stats_db_name}.dataset_sources; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.dataset_topics AS SELECT * FROM ${stats_db_name}.dataset_topics; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.datasource AS SELECT * FROM ${stats_db_name}.datasource; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.datasource_languages AS SELECT * FROM ${stats_db_name}.datasource_languages; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.datasource_oids AS SELECT * FROM ${stats_db_name}.datasource_oids; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.datasource_organizations AS SELECT * FROM ${stats_db_name}.datasource_organizations; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.datasource_results AS SELECT * FROM ${stats_db_name}.datasource_results; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.datasource_sources AS SELECT * FROM ${stats_db_name}.datasource_sources; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.funder AS SELECT * FROM ${stats_db_name}.funder; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.fundref AS SELECT * FROM ${stats_db_name}.fundref; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.numbers_country AS SELECT * FROM ${stats_db_name}.numbers_country; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.organization AS SELECT * FROM ${stats_db_name}.organization; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.organization_datasources AS SELECT * FROM ${stats_db_name}.organization_datasources; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.organization_pids AS SELECT * FROM ${stats_db_name}.organization_pids; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.organization_projects AS SELECT * FROM ${stats_db_name}.organization_projects; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.organization_sources AS SELECT * FROM ${stats_db_name}.organization_sources; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct AS SELECT * FROM ${stats_db_name}.otherresearchproduct; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_citations AS SELECT * FROM ${stats_db_name}.otherresearchproduct_citations; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_classifications AS SELECT * FROM ${stats_db_name}.otherresearchproduct_classifications; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_concepts AS SELECT * FROM ${stats_db_name}.otherresearchproduct_concepts; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_datasources AS SELECT * FROM ${stats_db_name}.otherresearchproduct_datasources; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_languages AS SELECT * FROM ${stats_db_name}.otherresearchproduct_languages; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_licenses AS SELECT * FROM ${stats_db_name}.otherresearchproduct_licenses; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_oids AS SELECT * FROM ${stats_db_name}.otherresearchproduct_oids; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_pids AS SELECT * FROM ${stats_db_name}.otherresearchproduct_pids; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_refereed AS SELECT * FROM ${stats_db_name}.otherresearchproduct_refereed; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_sources AS SELECT * FROM ${stats_db_name}.otherresearchproduct_sources; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.otherresearchproduct_topics AS SELECT * FROM ${stats_db_name}.otherresearchproduct_topics; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.project AS SELECT * FROM ${stats_db_name}.project; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.project_oids AS SELECT * FROM ${stats_db_name}.project_oids; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.project_organizations AS SELECT * FROM ${stats_db_name}.project_organizations; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.project_results AS SELECT * FROM ${stats_db_name}.project_results; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.project_resultcount AS SELECT * FROM ${stats_db_name}.project_resultcount; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.project_results_publication AS SELECT * FROM ${stats_db_name}.project_results_publication; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication AS SELECT * FROM ${stats_db_name}.publication; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_citations AS SELECT * FROM ${stats_db_name}.publication_citations; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_classifications AS SELECT * FROM ${stats_db_name}.publication_classifications; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_concepts AS SELECT * FROM ${stats_db_name}.publication_concepts; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_datasources AS SELECT * FROM ${stats_db_name}.publication_datasources; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_languages AS SELECT * FROM ${stats_db_name}.publication_languages; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_licenses AS SELECT * FROM ${stats_db_name}.publication_licenses; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_oids AS SELECT * FROM ${stats_db_name}.publication_oids; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_pids AS SELECT * FROM ${stats_db_name}.publication_pids; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_refereed AS SELECT * FROM ${stats_db_name}.publication_refereed; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_sources AS SELECT * FROM ${stats_db_name}.publication_sources; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.publication_topics AS SELECT * FROM ${stats_db_name}.publication_topics; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result AS SELECT * FROM ${stats_db_name}.result; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_affiliated_country AS SELECT * FROM ${stats_db_name}.result_affiliated_country; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_citations AS SELECT * FROM ${stats_db_name}.result_citations; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_classifications AS SELECT * FROM ${stats_db_name}.result_classifications; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_concepts AS SELECT * FROM ${stats_db_name}.result_concepts; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_datasources AS SELECT * FROM ${stats_db_name}.result_datasources; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_deposited_country AS SELECT * FROM ${stats_db_name}.result_deposited_country; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_fundercount AS SELECT * FROM ${stats_db_name}.result_fundercount; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_gold AS SELECT * FROM ${stats_db_name}.result_gold; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_greenoa AS SELECT * FROM ${stats_db_name}.result_greenoa; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_languages AS SELECT * FROM ${stats_db_name}.result_languages; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_licenses AS SELECT * FROM ${stats_db_name}.result_licenses; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_oids AS SELECT * FROM ${stats_db_name}.result_oids; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_organization AS SELECT * FROM ${stats_db_name}.result_organization; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_peerreviewed AS SELECT * FROM ${stats_db_name}.result_peerreviewed; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_pids AS SELECT * FROM ${stats_db_name}.result_pids; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_projectcount AS SELECT * FROM ${stats_db_name}.result_projectcount; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_projects AS SELECT * FROM ${stats_db_name}.result_projects; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_refereed AS SELECT * FROM ${stats_db_name}.result_refereed; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_sources AS SELECT * FROM ${stats_db_name}.result_sources; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.result_topics AS SELECT * FROM ${stats_db_name}.result_topics; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.rndexpediture AS SELECT * FROM ${stats_db_name}.rndexpediture; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.roarmap AS SELECT * FROM ${stats_db_name}.roarmap; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software AS SELECT * FROM ${stats_db_name}.software; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_citations AS SELECT * FROM ${stats_db_name}.software_citations; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_classifications AS SELECT * FROM ${stats_db_name}.software_classifications; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_concepts AS SELECT * FROM ${stats_db_name}.software_concepts; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_datasources AS SELECT * FROM ${stats_db_name}.software_datasources; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_languages AS SELECT * FROM ${stats_db_name}.software_languages; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_licenses AS SELECT * FROM ${stats_db_name}.software_licenses; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_oids AS SELECT * FROM ${stats_db_name}.software_oids; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_pids AS SELECT * FROM ${stats_db_name}.software_pids; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_refereed AS SELECT * FROM ${stats_db_name}.software_refereed; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_sources AS SELECT * FROM ${stats_db_name}.software_sources; -CREATE VIEW IF NOT EXISTS ${stats_db_production_name}.software_topics AS SELECT * FROM ${stats_db_name}.software_topics; diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/updateProductionViews.sh b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/updateProductionViews.sh new file mode 100644 index 000000000..3e510e87e --- /dev/null +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/updateProductionViews.sh @@ -0,0 +1,16 @@ +export PYTHON_EGG_CACHE=/home/$(whoami)/.python-eggs +export link_folder=/tmp/impala-shell-python-egg-cache-$(whoami) +if ! [ -L $link_folder ] +then + rm -Rf "$link_folder" + ln -sfn ${PYTHON_EGG_CACHE}${link_folder} ${link_folder} +fi + +export SOURCE=$1 +export PRODUCTION=$2 + +echo "Updating ${PRODUCTION} database" +impala-shell -q "create database if not exists ${PRODUCTION}" +impala-shell -d ${PRODUCTION} -q "show tables" --delimited | sed "s/^/drop view if exists ${PRODUCTION}./" | sed "s/$/;/" | impala-shell -c -f - +impala-shell -d ${SOURCE} -q "show tables" --delimited | sed "s/\(.*\)/create view ${PRODUCTION}.\1 as select * from ${SOURCE}.\1;/" | impala-shell -c -f - +echo "Production db ready!" \ No newline at end of file diff --git a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml index d744f18da..0d8ff7ee3 100644 --- a/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-stats-promote/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml @@ -6,7 +6,15 @@ stats_db_production_name - the name of the production schema + the name of the public production schema + + + monitor_db_name + the monitor database name + + + monitor_db_production_name + the name of the monitor public database stats_tool_api_url @@ -48,25 +56,26 @@ - - ${hive_jdbc_url} - - stats_db_name=${stats_db_name} - stats_db_production_name=${stats_db_production_name} - - - - - - ${jobTracker} ${nameNode} - impala-shell.sh + updateProductionViews.sh + ${stats_db_name} ${stats_db_production_name} - computeProductionStats.sql - ${wf:appPath()}/scripts/computeProductionStats.sql - impala-shell.sh + updateProductionViews.sh + + + + + + + + ${jobTracker} + ${nameNode} + updateProductionViews.sh + ${monitor_db_name} + ${monitor_db_production_name} + updateProductionViews.sh diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh new file mode 100644 index 000000000..29b225e3c --- /dev/null +++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/contexts.sh @@ -0,0 +1,43 @@ +export PYTHON_EGG_CACHE=/home/$(whoami)/.python-eggs +export link_folder=/tmp/impala-shell-python-egg-cache-$(whoami) +if ! [ -L $link_folder ] +then + rm -Rf "$link_folder" + ln -sfn ${PYTHON_EGG_CACHE}${link_folder} ${link_folder} +fi + +CONTEXT_API=$1 +TARGET_DB=$2 + +TMP=/tmp/stats-update-`tr -dc A-Za-z0-9 contexts.csv +cat contexts.csv | cut -d , -f1 | xargs -I {} curl -L ${CONTEXT_API}/context/{}/?all=true | /usr/local/sbin/jq -r '.[]|"\(.id|split(":")[0]),\(.id),\(.label)"' > categories.csv +cat categories.csv | cut -d , -f2 | sed 's/:/%3A/g'| xargs -I {} curl -L ${CONTEXT_API}/context/category/{}/?all=true | /usr/local/sbin/jq -r '.[]|"\(.id|split("::")[0])::\(.id|split("::")[1]),\(.id),\(.label)"' > concepts.csv +cat contexts.csv | sed 's/^\(.*\),\(.*\)/\1,\1::other,\2/' >> categories.csv +cat categories.csv | grep -v ::other | sed 's/^.*,\(.*\),\(.*\)/\1,\1::other,\2/' >> concepts.csv + +echo "uploading context data to hdfs" +hdfs dfs -mkdir ${TMP} +hdfs dfs -copyFromLocal contexts.csv ${TMP} +hdfs dfs -copyFromLocal categories.csv ${TMP} +hdfs dfs -copyFromLocal concepts.csv ${TMP} +hdfs dfs -chmod -R 777 ${TMP} + +echo "Creating and populating impala tables" +impala-shell -q "create table ${TARGET_DB}.context (id string, name string) row format delimited fields terminated by ','" +impala-shell -q "create table ${TARGET_DB}.category (context string, id string, name string) row format delimited fields terminated by ','" +impala-shell -q "create table ${TARGET_DB}.concept (category string, id string, name string) row format delimited fields terminated by ','" +impala-shell -d ${TARGET_DB} -q "invalidate metadata" +impala-shell -q "load data inpath '${TMP}/contexts.csv' into table ${TARGET_DB}.context" +impala-shell -q "load data inpath '${TMP}/categories.csv' into table ${TARGET_DB}.category" +impala-shell -q "load data inpath '${TMP}/concepts.csv' into table ${TARGET_DB}.concept" + +echo "Cleaning up" +hdfs dfs -rm -f -r -skipTrash ${TMP} +rm concepts.csv +rm categories.csv +rm contexts.csv + +echo "Finito!" \ No newline at end of file diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step10.sql b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step10.sql index 6c96317e6..77fbd3b18 100644 --- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step10.sql +++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step10.sql @@ -23,19 +23,6 @@ CREATE OR REPLACE VIEW ${stats_db_name}.rndexpediture AS SELECT * FROM ${external_stats_db_name}.rndexpediture; -CREATE OR REPLACE VIEW ${stats_db_name}.context AS -SELECT * -FROM ${external_stats_db_name}.context; - -CREATE OR REPLACE VIEW ${stats_db_name}.category AS -SELECT * -FROM ${external_stats_db_name}.category; - -CREATE OR REPLACE VIEW ${stats_db_name}.concept AS -SELECT * -FROM ${external_stats_db_name}.concept; - - ------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------ -- Creation date of the database diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step2.sql b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step2.sql index 62a158560..75b24b189 100644 --- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step2.sql +++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step2.sql @@ -47,7 +47,10 @@ from ${openaire_db_name}.publication p where p.datainfo.deletedbyinference = false; CREATE TABLE ${stats_db_name}.publication_concepts AS -SELECT substr(p.id, 4) as id, contexts.context.id as concept +SELECT substr(p.id, 4) as id, case + when contexts.context.id RLIKE '^[^::]+::[^::]+::.+$' then contexts.context.id + when contexts.context.id RLIKE '^[^::]+::[^::]+$' then concat(contexts.context.id, '::other') + when contexts.context.id RLIKE '^[^::]+$' then concat(contexts.context.id, '::other::other') END as concept from ${openaire_db_name}.publication p LATERAL VIEW explode(p.context) contexts as context where p.datainfo.deletedbyinference = false; diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step3.sql b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step3.sql index dcd5ad858..540cc03a5 100644 --- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step3.sql +++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step3.sql @@ -54,7 +54,10 @@ FROM ${openaire_db_name}.dataset p where p.datainfo.deletedbyinference = false; CREATE TABLE ${stats_db_name}.dataset_concepts AS -SELECT substr(p.id, 4) as id, contexts.context.id as concept +SELECT substr(p.id, 4) as id, case + when contexts.context.id RLIKE '^[^::]+::[^::]+::.+$' then contexts.context.id + when contexts.context.id RLIKE '^[^::]+::[^::]+$' then concat(contexts.context.id, '::other') + when contexts.context.id RLIKE '^[^::]+$' then concat(contexts.context.id, '::other::other') END as concept from ${openaire_db_name}.dataset p LATERAL VIEW explode(p.context) contexts as context where p.datainfo.deletedbyinference = false; diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step4.sql b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step4.sql index fd5390e66..54345e074 100644 --- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step4.sql +++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step4.sql @@ -54,7 +54,10 @@ FROM ${openaire_db_name}.software p where p.datainfo.deletedbyinference = false; CREATE TABLE ${stats_db_name}.software_concepts AS -SELECT substr(p.id, 4) AS id, contexts.context.id AS concept +SELECT substr(p.id, 4) as id, case + when contexts.context.id RLIKE '^[^::]+::[^::]+::.+$' then contexts.context.id + when contexts.context.id RLIKE '^[^::]+::[^::]+$' then concat(contexts.context.id, '::other') + when contexts.context.id RLIKE '^[^::]+$' then concat(contexts.context.id, '::other::other') END as concept FROM ${openaire_db_name}.software p LATERAL VIEW explode(p.context) contexts AS context where p.datainfo.deletedbyinference = false; diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step5.sql b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step5.sql index b359b596f..36ad5d92a 100644 --- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step5.sql +++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/scripts/step5.sql @@ -52,7 +52,10 @@ FROM ${openaire_db_name}.otherresearchproduct p LATERAL VIEW explode(p.instance. where p.datainfo.deletedbyinference = false; CREATE TABLE ${stats_db_name}.otherresearchproduct_concepts AS -SELECT substr(p.id, 4) AS id, contexts.context.id AS concept +SELECT substr(p.id, 4) as id, case + when contexts.context.id RLIKE '^[^::]+::[^::]+::.+$' then contexts.context.id + when contexts.context.id RLIKE '^[^::]+::[^::]+$' then concat(contexts.context.id, '::other') + when contexts.context.id RLIKE '^[^::]+$' then concat(contexts.context.id, '::other::other') END as concept FROM ${openaire_db_name}.otherresearchproduct p LATERAL VIEW explode(p.context) contexts AS context where p.datainfo.deletedbyinference = false; diff --git a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml index 9c16f149d..321500e2c 100644 --- a/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-stats-update/src/main/resources/eu/dnetlib/dhp/oa/graph/stats/oozie_app/workflow.xml @@ -41,6 +41,10 @@ hive_timeout the time period, in seconds, after which Hive fails a transaction if a Hive client has not sent a hearbeat. The default value is 300 seconds. + + context_api_url + the base url of the context api (https://services.openaire.eu/openaire) + @@ -260,6 +264,19 @@ stats_db_name=${stats_db_name} openaire_db_name=${openaire_db_name} + + + + + + + ${jobTracker} + ${nameNode} + contexts.sh + ${context_api_url} + ${stats_db_name} + contexts.sh + From 3becaa5539aee14592873f90ebfd7275206f4f61 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 29 Mar 2021 16:01:35 +0200 Subject: [PATCH 186/445] [Cleaning] drop alternate identifiers with empty values --- .../main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index 3be062c0c..401d5d444 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -155,12 +155,14 @@ public class CleaningFunctions { final Set pids = pid .stream() + .filter(Objects::nonNull) .filter(p -> StringUtils.isNotBlank(p.getValue())) .collect(Collectors.toCollection(HashSet::new)); Optional.ofNullable(i.getAlternateIdentifier()) .ifPresent(altId -> { final Set altIds = altId.stream() + .filter(Objects::nonNull) .filter(p -> StringUtils.isNotBlank(p.getValue())) .collect(Collectors.toCollection(HashSet::new)); From f446580e9fe5b1f4be898ddb77587435465648d3 Mon Sep 17 00:00:00 2001 From: miconis Date: Mon, 29 Mar 2021 16:10:46 +0200 Subject: [PATCH 187/445] code refactoring (useless classes and wf removed), implementation of the test for the openorgs dedup --- dhp-workflows/dhp-dedup-openaire/pom.xml | 6 + .../dhp/oa/dedup/SparkCollectSimRels.java | 184 -------- .../oa/dedup/SparkCopyOpenorgsSimRels.java | 1 + .../oa/dedup/collectSimRels_parameters.json | 44 -- .../neworgs/oozie_app/config-default.xml | 18 - .../oa/dedup/neworgs/oozie_app/workflow.xml | 208 --------- .../orgsdedup/oozie_app/config-default.xml | 18 - .../oa/dedup/orgsdedup/oozie_app/workflow.xml | 240 ----------- .../dnetlib/dhp/oa/dedup/SparkDedupTest.java | 78 +--- .../dhp/oa/dedup/SparkOpenorgsDedupTest.java | 408 ++++++++++++++++++ ...39-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz | Bin 0 -> 4844 bytes ...39-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz | Bin 0 -> 3428 bytes ...39-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz | Bin 0 -> 3191 bytes ...9f-4ae6-4db9-919d-85ddc0a60f92-c000.txt.gz | Bin 0 -> 683 bytes ...9f-4ae6-4db9-919d-85ddc0a60f92-c000.txt.gz | Bin 0 -> 1755 bytes .../graph/sql/queryOpenOrgsForOrgsDedup.sql | 4 +- .../queryOpenOrgsSimilarityForOrgsDedup.sql | 2 +- .../dhp/oa/graph/sql/queryOrganizations.sql | 2 +- 18 files changed, 424 insertions(+), 789 deletions(-) delete mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCollectSimRels.java delete mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/collectSimRels_parameters.json delete mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/neworgs/oozie_app/config-default.xml delete mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/neworgs/oozie_app/workflow.xml delete mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/config-default.xml delete mode 100644 dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/workflow.xml create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsDedupTest.java create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/organization/part-00000-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/organization/part-00001-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/organization/part-00002-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/relation/part-00000-94553c9f-4ae6-4db9-919d-85ddc0a60f92-c000.txt.gz create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/relation/part-00003-94553c9f-4ae6-4db9-919d-85ddc0a60f92-c000.txt.gz diff --git a/dhp-workflows/dhp-dedup-openaire/pom.xml b/dhp-workflows/dhp-dedup-openaire/pom.xml index 04e158542..52cc149a9 100644 --- a/dhp-workflows/dhp-dedup-openaire/pom.xml +++ b/dhp-workflows/dhp-dedup-openaire/pom.xml @@ -94,6 +94,12 @@ org.apache.httpcomponents httpclient + + com.h2database + h2 + 1.4.200 + test + diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCollectSimRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCollectSimRels.java deleted file mode 100644 index f9e6448b0..000000000 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCollectSimRels.java +++ /dev/null @@ -1,184 +0,0 @@ - -package eu.dnetlib.dhp.oa.dedup; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -import org.apache.commons.io.IOUtils; -import org.apache.spark.SparkConf; -import org.apache.spark.api.java.JavaPairRDD; -import org.apache.spark.api.java.JavaRDD; -import org.apache.spark.sql.*; -import org.dom4j.DocumentException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.schema.oaf.DataInfo; -import eu.dnetlib.dhp.schema.oaf.Relation; -import eu.dnetlib.dhp.utils.ISLookupClientFactory; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; -import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import eu.dnetlib.pace.config.DedupConfig; -import scala.Tuple2; - -public class SparkCollectSimRels extends AbstractSparkAction { - - private static final Logger log = LoggerFactory.getLogger(SparkCollectSimRels.class); - - Dataset simGroupsDS; - Dataset groupsDS; - - public SparkCollectSimRels(ArgumentApplicationParser parser, SparkSession spark, Dataset simGroupsDS, - Dataset groupsDS) { - super(parser, spark); - this.simGroupsDS = simGroupsDS; - this.groupsDS = groupsDS; - } - - public static void main(String[] args) throws Exception { - ArgumentApplicationParser parser = new ArgumentApplicationParser( - IOUtils - .toString( - SparkBlockStats.class - .getResourceAsStream( - "/eu/dnetlib/dhp/oa/dedup/collectSimRels_parameters.json"))); - parser.parseArgument(args); - - SparkConf conf = new SparkConf(); - - final String dbUrl = parser.get("postgresUrl"); - final String dbUser = parser.get("postgresUser"); - final String dbPassword = parser.get("postgresPassword"); - - SparkSession spark = getSparkSession(conf); - - DataFrameReader readOptions = spark - .read() - .format("jdbc") - .option("url", dbUrl) - .option("user", dbUser) - .option("password", dbPassword); - - new SparkCollectSimRels( - parser, - spark, - readOptions.option("dbtable", "similarity_groups").load(), - readOptions.option("dbtable", "groups").load()) - .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); - } - - @Override - void run(ISLookUpService isLookUpService) throws DocumentException, ISLookUpException, IOException { - - // read oozie parameters - final String isLookUpUrl = parser.get("isLookUpUrl"); - final String actionSetId = parser.get("actionSetId"); - final String workingPath = parser.get("workingPath"); - final int numPartitions = Optional - .ofNullable(parser.get("numPartitions")) - .map(Integer::valueOf) - .orElse(NUM_PARTITIONS); - final String dbUrl = parser.get("postgresUrl"); - final String dbUser = parser.get("postgresUser"); - - log.info("numPartitions: '{}'", numPartitions); - log.info("isLookUpUrl: '{}'", isLookUpUrl); - log.info("actionSetId: '{}'", actionSetId); - log.info("workingPath: '{}'", workingPath); - log.info("postgresUser: {}", dbUser); - log.info("postgresUrl: {}", dbUrl); - log.info("postgresPassword: xxx"); - - JavaPairRDD> similarityGroup = simGroupsDS - .toJavaRDD() - .mapToPair(r -> new Tuple2<>(r.getString(0), r.getString(1))) - .groupByKey() - .mapToPair( - i -> new Tuple2<>(i._1(), StreamSupport - .stream(i._2().spliterator(), false) - .collect(Collectors.toList()))); - - JavaPairRDD groupIds = groupsDS - .toJavaRDD() - .mapToPair(r -> new Tuple2<>(r.getString(0), r.getString(1))); - - JavaRDD, List>> groups = similarityGroup - .leftOuterJoin(groupIds) - .filter(g -> g._2()._2().isPresent()) - .map(g -> new Tuple2<>(new Tuple2<>(g._1(), g._2()._2().get()), g._2()._1())); - - JavaRDD relations = groups.flatMap(g -> { - String firstId = g._2().get(0); - List rels = new ArrayList<>(); - - for (String id : g._2()) { - if (!firstId.equals(id)) - rels.add(createSimRel(firstId, id, g._1()._2())); - } - - return rels.iterator(); - }); - - Dataset resultRelations = spark - .createDataset( - relations.filter(r -> r.getRelType().equals("resultResult")).rdd(), - Encoders.bean(Relation.class)) - .repartition(numPartitions); - - Dataset organizationRelations = spark - .createDataset( - relations.filter(r -> r.getRelType().equals("organizationOrganization")).rdd(), - Encoders.bean(Relation.class)) - .repartition(numPartitions); - - for (DedupConfig dedupConf : getConfigurations(isLookUpService, actionSetId)) { - switch (dedupConf.getWf().getSubEntityValue()) { - case "organization": - savePostgresRelation(organizationRelations, workingPath, actionSetId, "organization"); - break; - default: - savePostgresRelation( - resultRelations, workingPath, actionSetId, dedupConf.getWf().getSubEntityValue()); - break; - } - } - - } - - private Relation createSimRel(String source, String target, String entity) { - final Relation r = new Relation(); - r.setSubRelType("dedupSimilarity"); - r.setRelClass("isSimilarTo"); - r.setDataInfo(new DataInfo()); - - switch (entity) { - case "result": - r.setSource("50|" + source); - r.setTarget("50|" + target); - r.setRelType("resultResult"); - break; - case "organization": - r.setSource("20|" + source); - r.setTarget("20|" + target); - r.setRelType("organizationOrganization"); - break; - default: - throw new IllegalArgumentException("unmanaged entity type: " + entity); - } - return r; - } - - private void savePostgresRelation(Dataset newRelations, String workingPath, String actionSetId, - String entityType) { - newRelations - .write() - .mode(SaveMode.Append) - .parquet(DedupUtility.createSimRelPath(workingPath, actionSetId, entityType)); - } - -} diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java index 8cffacd7e..dbcd40289 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java @@ -9,6 +9,7 @@ import java.util.Optional; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaRDD; +import org.apache.spark.api.java.function.ForeachFunction; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.graphx.Edge; import org.apache.spark.rdd.RDD; diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/collectSimRels_parameters.json b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/collectSimRels_parameters.json deleted file mode 100644 index da1011371..000000000 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/collectSimRels_parameters.json +++ /dev/null @@ -1,44 +0,0 @@ -[ - { - "paramName": "la", - "paramLongName": "isLookUpUrl", - "paramDescription": "address for the LookUp", - "paramRequired": true - }, - { - "paramName": "asi", - "paramLongName": "actionSetId", - "paramDescription": "action set identifier (name of the orchestrator)", - "paramRequired": true - }, - { - "paramName": "w", - "paramLongName": "workingPath", - "paramDescription": "path of the working directory", - "paramRequired": true - }, - { - "paramName": "np", - "paramLongName": "numPartitions", - "paramDescription": "number of partitions for the similarity relations intermediate phases", - "paramRequired": false - }, - { - "paramName": "purl", - "paramLongName": "postgresUrl", - "paramDescription": "the url of the postgres server", - "paramRequired": true - }, - { - "paramName": "pusr", - "paramLongName": "postgresUser", - "paramDescription": "the owner of the postgres database", - "paramRequired": true - }, - { - "paramName": "ppwd", - "paramLongName": "postgresPassword", - "paramDescription": "the password for the postgres user", - "paramRequired": true - } -] \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/neworgs/oozie_app/config-default.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/neworgs/oozie_app/config-default.xml deleted file mode 100644 index 2e0ed9aee..000000000 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/neworgs/oozie_app/config-default.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - jobTracker - yarnRM - - - nameNode - hdfs://nameservice1 - - - oozie.use.system.libpath - true - - - oozie.action.sharelib.for.spark - spark2 - - \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/neworgs/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/neworgs/oozie_app/workflow.xml deleted file mode 100644 index 9bfdaaebd..000000000 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/neworgs/oozie_app/workflow.xml +++ /dev/null @@ -1,208 +0,0 @@ - - - - graphBasePath - the raw graph base path - - - isLookUpUrl - the address of the lookUp service - - - actionSetId - id of the actionSet - - - workingPath - path for the working directory - - - dedupGraphPath - path for the output graph - - - cutConnectedComponent - max number of elements in a connected component - - - dbUrl - the url of the database - - - dbUser - the user of the database - - - dbTable - the name of the table in the database - - - dbPwd - the passowrd of the user of the database - - - sparkDriverMemory - memory for driver process - - - sparkExecutorMemory - memory for individual executor - - - sparkExecutorCores - number of cores used by single executor - - - oozieActionShareLibForSpark2 - oozie action sharelib for spark 2.* - - - spark2ExtraListeners - com.cloudera.spark.lineage.NavigatorAppListener - spark 2.* extra listeners classname - - - spark2SqlQueryExecutionListeners - com.cloudera.spark.lineage.NavigatorQueryListener - spark 2.* sql query execution listeners classname - - - spark2YarnHistoryServerAddress - spark 2.* yarn history server address - - - spark2EventLogDir - spark 2.* event log dir location - - - - - ${jobTracker} - ${nameNode} - - - mapreduce.job.queuename - ${queueName} - - - oozie.launcher.mapred.job.queue.name - ${oozieLauncherQueueName} - - - oozie.action.sharelib.for.spark - ${oozieActionShareLibForSpark2} - - - - - - - - Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] - - - - - - - - - - - - - -pb - ${graphBasePath}/relation - ${workingPath}/${actionSetId}/organization_simrel - - - - - - - - yarn - cluster - Create Similarity Relations - eu.dnetlib.dhp.oa.dedup.SparkCreateSimRels - dhp-dedup-openaire-${projectVersion}.jar - - --executor-memory=${sparkExecutorMemory} - --executor-cores=${sparkExecutorCores} - --driver-memory=${sparkDriverMemory} - --conf spark.extraListeners=${spark2ExtraListeners} - --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} - --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} - --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} - --conf spark.sql.shuffle.partitions=3840 - - --graphBasePath${graphBasePath} - --isLookUpUrl${isLookUpUrl} - --actionSetId${actionSetId} - --workingPath${workingPath} - --numPartitions8000 - - - - - - - - yarn - cluster - Create Merge Relations - eu.dnetlib.dhp.oa.dedup.SparkCreateMergeRels - dhp-dedup-openaire-${projectVersion}.jar - - --executor-memory=${sparkExecutorMemory} - --executor-cores=${sparkExecutorCores} - --driver-memory=${sparkDriverMemory} - --conf spark.extraListeners=${spark2ExtraListeners} - --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} - --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} - --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} - --conf spark.sql.shuffle.partitions=3840 - - --graphBasePath${graphBasePath} - --workingPath${workingPath} - --isLookUpUrl${isLookUpUrl} - --actionSetId${actionSetId} - --cutConnectedComponent${cutConnectedComponent} - - - - - - - - yarn - cluster - Prepare New Organizations - eu.dnetlib.dhp.oa.dedup.SparkPrepareNewOrgs - dhp-dedup-openaire-${projectVersion}.jar - - --executor-memory=${sparkExecutorMemory} - --executor-cores=${sparkExecutorCores} - --driver-memory=${sparkDriverMemory} - --conf spark.extraListeners=${spark2ExtraListeners} - --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} - --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} - --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} - --conf spark.sql.shuffle.partitions=3840 - - --graphBasePath${graphBasePath} - --workingPath${workingPath} - --isLookUpUrl${isLookUpUrl} - --actionSetId${actionSetId} - --dbUrl${dbUrl} - --dbTable${dbTable} - --dbUser${dbUser} - --dbPwd${dbPwd} - --numConnections20 - - - - - - - \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/config-default.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/config-default.xml deleted file mode 100644 index 2e0ed9aee..000000000 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/config-default.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - jobTracker - yarnRM - - - nameNode - hdfs://nameservice1 - - - oozie.use.system.libpath - true - - - oozie.action.sharelib.for.spark - spark2 - - \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/workflow.xml deleted file mode 100644 index e7c95ee8d..000000000 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/orgsdedup/oozie_app/workflow.xml +++ /dev/null @@ -1,240 +0,0 @@ - - - - graphBasePath - the raw graph base path - - - isLookUpUrl - the address of the lookUp service - - - actionSetId - id of the actionSet - - - workingPath - path for the working directory - - - dedupGraphPath - path for the output graph - - - cutConnectedComponent - max number of elements in a connected component - - - dbUrl - the url of the database - - - dbUser - the user of the database - - - dbTable - the name of the table in the database - - - dbPwd - the passowrd of the user of the database - - - sparkDriverMemory - memory for driver process - - - sparkExecutorMemory - memory for individual executor - - - sparkExecutorCores - number of cores used by single executor - - - oozieActionShareLibForSpark2 - oozie action sharelib for spark 2.* - - - spark2ExtraListeners - com.cloudera.spark.lineage.NavigatorAppListener - spark 2.* extra listeners classname - - - spark2SqlQueryExecutionListeners - com.cloudera.spark.lineage.NavigatorQueryListener - spark 2.* sql query execution listeners classname - - - spark2YarnHistoryServerAddress - spark 2.* yarn history server address - - - spark2EventLogDir - spark 2.* event log dir location - - - - - ${jobTracker} - ${nameNode} - - - mapreduce.job.queuename - ${queueName} - - - oozie.launcher.mapred.job.queue.name - ${oozieLauncherQueueName} - - - oozie.action.sharelib.for.spark - ${oozieActionShareLibForSpark2} - - - - - - - - Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] - - - - - - - - - - - - - -pb - /tmp/graph_openorgs_and_corda/relation - ${workingPath}/${actionSetId}/organization_simrel - - - - - - - - yarn - cluster - Create Similarity Relations - eu.dnetlib.dhp.oa.dedup.SparkCreateSimRels - dhp-dedup-openaire-${projectVersion}.jar - - --executor-memory=${sparkExecutorMemory} - --executor-cores=${sparkExecutorCores} - --driver-memory=${sparkDriverMemory} - --conf spark.extraListeners=${spark2ExtraListeners} - --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} - --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} - --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} - --conf spark.sql.shuffle.partitions=3840 - - --graphBasePath${graphBasePath} - --isLookUpUrl${isLookUpUrl} - --actionSetId${actionSetId} - --workingPath${workingPath} - --numPartitions8000 - - - - - - - - yarn - cluster - Create Merge Relations - eu.dnetlib.dhp.oa.dedup.SparkCreateMergeRels - dhp-dedup-openaire-${projectVersion}.jar - - --executor-memory=${sparkExecutorMemory} - --executor-cores=${sparkExecutorCores} - --driver-memory=${sparkDriverMemory} - --conf spark.extraListeners=${spark2ExtraListeners} - --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} - --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} - --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} - --conf spark.sql.shuffle.partitions=3840 - - --graphBasePath${graphBasePath} - --workingPath${workingPath} - --isLookUpUrl${isLookUpUrl} - --actionSetId${actionSetId} - --cutConnectedComponent${cutConnectedComponent} - - - - - - - - yarn - cluster - Prepare Organization Relations - eu.dnetlib.dhp.oa.dedup.SparkPrepareOrgRels - dhp-dedup-openaire-${projectVersion}.jar - - --executor-memory=${sparkExecutorMemory} - --executor-cores=${sparkExecutorCores} - --driver-memory=${sparkDriverMemory} - --conf spark.extraListeners=${spark2ExtraListeners} - --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} - --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} - --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} - --conf spark.sql.shuffle.partitions=3840 - - --graphBasePath${graphBasePath} - --workingPath${workingPath} - --isLookUpUrl${isLookUpUrl} - --actionSetId${actionSetId} - --dbUrl${dbUrl} - --dbTable${dbTable} - --dbUser${dbUser} - --dbPwd${dbPwd} - --numConnections20 - - - - - - - - yarn - cluster - Prepare New Organizations - eu.dnetlib.dhp.oa.dedup.SparkPrepareNewOrgs - dhp-dedup-openaire-${projectVersion}.jar - - --executor-memory=${sparkExecutorMemory} - --executor-cores=${sparkExecutorCores} - --driver-memory=${sparkDriverMemory} - --conf spark.extraListeners=${spark2ExtraListeners} - --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} - --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} - --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} - --conf spark.sql.shuffle.partitions=3840 - - --graphBasePath${graphBasePath} - --workingPath${workingPath} - --isLookUpUrl${isLookUpUrl} - --actionSetId${actionSetId} - --apiUrl${apiUrl} - --dbUrl${dbUrl} - --dbTable${dbTable} - --dbUser${dbUser} - --dbPwd${dbPwd} - --numConnections20 - - - - - - - \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java index 33da45feb..851e72dee 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkDedupTest.java @@ -204,76 +204,8 @@ public class SparkDedupTest implements Serializable { assertEquals(6750, orp_simrel); } - @Disabled @Test @Order(2) - public void collectSimRelsTest() throws Exception { - ArgumentApplicationParser parser = new ArgumentApplicationParser( - IOUtils - .toString( - SparkCollectSimRels.class - .getResourceAsStream( - "/eu/dnetlib/dhp/oa/dedup/collectSimRels_parameters.json"))); - parser - .parseArgument( - new String[] { - "-asi", testActionSetId, - "-la", "lookupurl", - "-w", testOutputBasePath, - "-np", "50", - "-purl", "jdbc:postgresql://localhost:5432/dnet_dedup", - "-pusr", "postgres_user", - "-ppwd", "" - }); - - new SparkCollectSimRels( - parser, - spark, - spark.read().load(testDedupAssertionsBasePath + "/similarity_groups"), - spark.read().load(testDedupAssertionsBasePath + "/groups")) - .run(isLookUpService); - - long orgs_simrel = spark - .read() - .load(testOutputBasePath + "/" + testActionSetId + "/organization_simrel") - .count(); - - long pubs_simrel = spark - .read() - .load(testOutputBasePath + "/" + testActionSetId + "/publication_simrel") - .count(); - - long sw_simrel = spark - .read() - .load(testOutputBasePath + "/" + testActionSetId + "/software_simrel") - .count(); - - long ds_simrel = spark - .read() - .load(testOutputBasePath + "/" + testActionSetId + "/dataset_simrel") - .count(); - - long orp_simrel = spark - .read() - .json(testOutputBasePath + "/" + testActionSetId + "/otherresearchproduct_simrel") - .count(); - -// System.out.println("orgs_simrel = " + orgs_simrel); -// System.out.println("pubs_simrel = " + pubs_simrel); -// System.out.println("sw_simrel = " + sw_simrel); -// System.out.println("ds_simrel = " + ds_simrel); -// System.out.println("orp_simrel = " + orp_simrel); - - assertEquals(3672, orgs_simrel); - assertEquals(10459, pubs_simrel); - assertEquals(3767, sw_simrel); - assertEquals(3865, ds_simrel); - assertEquals(10173, orp_simrel); - - } - - @Test - @Order(3) public void cutMergeRelsTest() throws Exception { ArgumentApplicationParser parser = new ArgumentApplicationParser( @@ -369,7 +301,7 @@ public class SparkDedupTest implements Serializable { } @Test - @Order(4) + @Order(3) public void createMergeRelsTest() throws Exception { ArgumentApplicationParser parser = new ArgumentApplicationParser( @@ -424,7 +356,7 @@ public class SparkDedupTest implements Serializable { } @Test - @Order(5) + @Order(4) public void createDedupRecordTest() throws Exception { ArgumentApplicationParser parser = new ArgumentApplicationParser( @@ -471,7 +403,7 @@ public class SparkDedupTest implements Serializable { } @Test - @Order(6) + @Order(5) public void updateEntityTest() throws Exception { ArgumentApplicationParser parser = new ArgumentApplicationParser( @@ -587,7 +519,7 @@ public class SparkDedupTest implements Serializable { } @Test - @Order(7) + @Order(6) public void propagateRelationTest() throws Exception { ArgumentApplicationParser parser = new ArgumentApplicationParser( @@ -637,7 +569,7 @@ public class SparkDedupTest implements Serializable { } @Test - @Order(8) + @Order(7) public void testRelations() throws Exception { testUniqueness("/eu/dnetlib/dhp/dedup/test/relation_1.json", 12, 10); testUniqueness("/eu/dnetlib/dhp/dedup/test/relation_2.json", 10, 2); diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsDedupTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsDedupTest.java new file mode 100644 index 000000000..f33eca57f --- /dev/null +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsDedupTest.java @@ -0,0 +1,408 @@ + +package eu.dnetlib.dhp.oa.dedup; + +import static java.nio.file.Files.createTempDirectory; + +import static org.apache.spark.sql.functions.count; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.lenient; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.net.URISyntaxException; +import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Properties; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaPairRDD; +import org.apache.spark.api.java.JavaRDD; +import org.apache.spark.api.java.JavaSparkContext; +import org.apache.spark.api.java.function.FilterFunction; +import org.apache.spark.api.java.function.ForeachFunction; +import org.apache.spark.api.java.function.MapFunction; +import org.apache.spark.api.java.function.PairFunction; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.Row; +import org.apache.spark.sql.SparkSession; +import org.apache.spark.util.CollectionsUtils; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.commons.util.StringUtils; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.oaf.OafMapperUtils; +import eu.dnetlib.dhp.schema.oaf.Organization; +import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.dhp.utils.DHPUtils; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; +import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import eu.dnetlib.pace.util.MapDocumentUtil; +import scala.Tuple2; + +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class SparkOpenorgsDedupTest implements Serializable { + + private static String dbUrl = "jdbc:h2:mem:openorgs_test;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false"; + private static String dbUser = "sa"; + private static String dbTable = "tmp_dedup_events"; + private static String dbPwd = ""; + + @Mock(serializable = true) + ISLookUpService isLookUpService; + + protected static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + private static SparkSession spark; + private static JavaSparkContext jsc; + + private static String testGraphBasePath; + private static String testOutputBasePath; + private static String testDedupGraphBasePath; + private static final String testActionSetId = "test-orchestrator-openorgs"; + + @BeforeAll + public static void cleanUp() throws IOException, URISyntaxException { + + testGraphBasePath = Paths + .get(SparkDedupTest.class.getResource("/eu/dnetlib/dhp/dedup/openorgs_dedup").toURI()) + .toFile() + .getAbsolutePath(); + testOutputBasePath = createTempDirectory(SparkDedupTest.class.getSimpleName() + "-") + .toAbsolutePath() + .toString(); + testDedupGraphBasePath = createTempDirectory(SparkDedupTest.class.getSimpleName() + "-") + .toAbsolutePath() + .toString(); + + FileUtils.deleteDirectory(new File(testOutputBasePath)); + FileUtils.deleteDirectory(new File(testDedupGraphBasePath)); + + final SparkConf conf = new SparkConf(); + conf.set("spark.sql.shuffle.partitions", "200"); + spark = SparkSession + .builder() + .appName(SparkDedupTest.class.getSimpleName()) + .master("local[*]") + .config(conf) + .getOrCreate(); + + jsc = JavaSparkContext.fromSparkContext(spark.sparkContext()); + + } + + @BeforeEach + public void setUp() throws IOException, ISLookUpException { + + lenient() + .when(isLookUpService.getResourceProfileByQuery(Mockito.contains(testActionSetId))) + .thenReturn( + IOUtils + .toString( + SparkDedupTest.class + .getResourceAsStream( + "/eu/dnetlib/dhp/dedup/profiles/mock_orchestrator_openorgs.xml"))); + + lenient() + .when(isLookUpService.getResourceProfileByQuery(Mockito.contains("organization"))) + .thenReturn( + IOUtils + .toString( + SparkDedupTest.class + .getResourceAsStream( + "/eu/dnetlib/dhp/dedup/conf/org.curr.conf.json"))); + } + + @Test + @Order(1) + public void createSimRelsTest() throws Exception { + + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCreateSimRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/createSimRels_parameters.json"))); + + parser + .parseArgument( + new String[] { + "-i", testGraphBasePath, + "-asi", testActionSetId, + "-la", "lookupurl", + "-w", testOutputBasePath, + "-np", "50" + }); + + new SparkCreateSimRels(parser, spark).run(isLookUpService); + + long orgs_simrel = spark + .read() + .load(DedupUtility.createSimRelPath(testOutputBasePath, testActionSetId, "organization")) + .count(); + + assertEquals(288, orgs_simrel); + } + + @Test + @Order(2) + public void copyOpenorgsSimRels() throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCopyOpenorgsSimRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/copyOpenorgsMergeRels_parameters.json"))); + parser + .parseArgument( + new String[] { + "-i", testGraphBasePath, + "-asi", testActionSetId, + "-w", testOutputBasePath, + "-la", "lookupurl", + "-np", "50" + }); + + new SparkCopyOpenorgsSimRels(parser, spark).run(isLookUpService); + + long orgs_simrel = spark + .read() + .load(DedupUtility.createSimRelPath(testOutputBasePath, testActionSetId, "organization")) + .count(); + + assertEquals(324, orgs_simrel); + } + + @Test + @Order(3) + public void createMergeRelsTest() throws Exception { + + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCreateMergeRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/createCC_parameters.json"))); + + parser + .parseArgument( + new String[] { + "-i", + testGraphBasePath, + "-asi", + testActionSetId, + "-la", + "lookupurl", + "-w", + testOutputBasePath + }); + + new SparkCreateMergeRels(parser, spark).run(isLookUpService); + + long orgs_mergerel = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/organization_mergerel") + .count(); + assertEquals(132, orgs_mergerel); + + // verify that a DiffRel is in the mergerels (to be sure that the job supposed to remove them has something to + // do) + List diffRels = jsc + .textFile(DedupUtility.createEntityPath(testGraphBasePath, "relation")) + .map(s -> OBJECT_MAPPER.readValue(s, Relation.class)) + .filter(r -> r.getRelClass().equals("isDifferentFrom")) + .map(r -> r.getTarget()) + .collect(); + assertEquals(18, diffRels.size()); + + List mergeRels = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/organization_mergerel") + .as(Encoders.bean(Relation.class)) + .toJavaRDD() + .map(r -> r.getTarget()) + .collect(); + assertFalse(Collections.disjoint(mergeRels, diffRels)); + + } + + @Test + @Order(4) + public void prepareOrgRelsTest() throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCreateSimRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/prepareOrgRels_parameters.json"))); + parser + .parseArgument( + new String[] { + "-i", + testGraphBasePath, + "-asi", + testActionSetId, + "-la", + "lookupurl", + "-w", + testOutputBasePath, + "-du", + dbUrl, + "-dusr", + dbUser, + "-t", + dbTable, + "-dpwd", + dbPwd + }); + + new SparkPrepareOrgRels(parser, spark).run(isLookUpService); + + final Properties connectionProperties = new Properties(); + connectionProperties.put("user", dbUser); + connectionProperties.put("password", dbPwd); + + Connection connection = DriverManager.getConnection(dbUrl, connectionProperties); + + ResultSet resultSet = connection + .prepareStatement("SELECT COUNT(*) as total_rels FROM " + dbTable) + .executeQuery(); + if (resultSet.next()) { + int total_rels = resultSet.getInt("total_rels"); + assertEquals(32, total_rels); + } else + fail("No result in the sql DB"); + resultSet.close(); + + // verify the number of organizations with duplicates + ResultSet resultSet2 = connection + .prepareStatement("SELECT COUNT(DISTINCT(local_id)) as total_orgs FROM " + dbTable) + .executeQuery(); + if (resultSet2.next()) { + int total_orgs = resultSet2.getInt("total_orgs"); + assertEquals(6, total_orgs); + } else + fail("No result in the sql DB"); + resultSet2.close(); + + // verify that no DiffRel is in the DB + List diffRels = jsc + .textFile(DedupUtility.createEntityPath(testGraphBasePath, "relation")) + .map(s -> OBJECT_MAPPER.readValue(s, Relation.class)) + .filter(r -> r.getRelClass().equals("isDifferentFrom")) + .map(r -> r.getSource() + "@@@" + r.getTarget()) + .collect(); + + List dbRels = new ArrayList<>(); + ResultSet resultSet3 = connection + .prepareStatement("SELECT local_id, oa_original_id FROM " + dbTable) + .executeQuery(); + while (resultSet3.next()) { + String source = OafMapperUtils.createOpenaireId("organization", resultSet3.getString("local_id"), true); + String target = OafMapperUtils + .createOpenaireId("organization", resultSet3.getString("oa_original_id"), true); + dbRels.add(source + "@@@" + target); + } + resultSet3.close(); + assertTrue(Collections.disjoint(dbRels, diffRels)); + + connection.close(); + } + + @Test + @Order(5) + public void prepareNewOrgsTest() throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkCreateSimRels.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/prepareNewOrgs_parameters.json"))); + parser + .parseArgument( + new String[] { + "-i", + testGraphBasePath, + "-asi", + testActionSetId, + "-la", + "lookupurl", + "-w", + testOutputBasePath, + "-du", + dbUrl, + "-dusr", + dbUser, + "-t", + dbTable, + "-dpwd", + dbPwd + }); + + new SparkPrepareNewOrgs(parser, spark).run(isLookUpService); + + final Properties connectionProperties = new Properties(); + connectionProperties.put("user", dbUser); + connectionProperties.put("password", dbPwd); + + long orgs_in_diffrel = jsc + .textFile(DedupUtility.createEntityPath(testGraphBasePath, "relation")) + .map(s -> OBJECT_MAPPER.readValue(s, Relation.class)) + .filter(r -> r.getRelClass().equals("isDifferentFrom")) + .map(r -> r.getTarget()) + .distinct() + .count(); + + Connection connection = DriverManager.getConnection(dbUrl, connectionProperties); + + jsc + .textFile(DedupUtility.createEntityPath(testGraphBasePath, "relation")) + .map(s -> OBJECT_MAPPER.readValue(s, Relation.class)) + .filter(r -> r.getRelClass().equals("isDifferentFrom")) + .map(r -> r.getTarget()) + .distinct() + .foreach(s -> System.out.println("difforgs = " + s)); + ResultSet resultSet0 = connection + .prepareStatement("SELECT oa_original_id FROM " + dbTable + " WHERE local_id = ''") + .executeQuery(); + while (resultSet0.next()) + System.out + .println( + "dborgs = " + OafMapperUtils.createOpenaireId(20, resultSet0.getString("oa_original_id"), true)); + resultSet0.close(); + + ResultSet resultSet = connection + .prepareStatement("SELECT COUNT(*) as total_new_orgs FROM " + dbTable + " WHERE local_id = ''") + .executeQuery(); + if (resultSet.next()) { + int total_new_orgs = resultSet.getInt("total_new_orgs"); + assertEquals(orgs_in_diffrel + 1, total_new_orgs); + } else + fail("No result in the sql DB"); + resultSet.close(); + } + + @AfterAll + public static void finalCleanUp() throws IOException { + FileUtils.deleteDirectory(new File(testOutputBasePath)); + FileUtils.deleteDirectory(new File(testDedupGraphBasePath)); + } + +} diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/organization/part-00000-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/organization/part-00000-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz new file mode 100644 index 0000000000000000000000000000000000000000..ba58d823c16070b42dad4d612051d2996f74cc27 GIT binary patch literal 4844 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

%+$z#WMe@wIZm@-y!Nfp%7X ztwNi|sDa?y?M$*Ql0k=a>gRf%Dcv?@oI0 zKUaI6>{5qCr9`EOGYkEa`xR*5lf9h1EPWGr$^8JXT)15nf^(ki0+pETN|iIZihWA# znP|ORR>Iu+^C#xUhB5Kf+%00JLQ0FNcyw2b+(9pphBOKwCX<_&x8#c|3Pq%a)o%a# zZ%iwN#5v0u&H7g!>T~DFrcd5)6PK8(_rGtwsZ|nmxgx+;gjuNB*DHn6sQ)jSi}fi7oWI zrQS?D`>U(F)bw9Bc5)xr%T!rZ3x>lJ$92q9WVWZDf?UMuwx#ar`6{6nLau6H1f_Uo zF@leEu8AUf?Nw1ZhqS{vg3w18K_pTv7lbt%4O%OFIYxkHJUZpL?pE9Y`=`y7Ck8}q z&XW3=U6dXp2t=pzZ$NZH{TH1;Fd#bRD4~G+?POr{qPPxb5TM=!7x5IO<7<=}c%A>= zUe>&K#nG+S0hN=OKpc910mknzPG2TtScQHJk3)Y4VO2R|oeW;7`8XMT{Cv}#1=bM> zf^9S9zzjU$P6jNjmLO5&ZcE15;L+^h8G+)_a@uYmoqtnUvtp~UksmassN7HhLfk^C&k<6sH1)p-IcMGZ7Jv}9kc9H( zYU&A$-tbq=MkAh|Da-^Hbm+C`zX~_;`EItgi56`U?kI`7s-Xq3u>Fs2n0JmxY)hl{z~A1P#=%oaccNN=k$<<73b78XLT&XEN6B*;MYWml zZz^s|8=3}~9?Q|S?Mzf|W2hVPX_F;kO&41CKw zPV~THCbo}Ft4Xboy4h&Pa3D@7p8*I3vx)s}=F&p`U&wE6PwlCa}ho2EkSi9tEYn z1j&ThLf=AFIp1@bp7Are|j^!c2vC- zz#hpgc&sfZ-{~-L<=++X%zfsK^SyZc^BlVblin#Xi_oJH!!WeCj6C#+*B)3Xs6dY@ za(er^&xkC|hhp#L4%<4}GA;zl1qLQOnJ3H>P&rq?4B(dG+7NaH1?&7j+kdu?ZbZa& z{xB_zW4Pl*aDAqMb!}%k5J@)G<|StF?>9{iCg>aa51+8b2&l+H(&^PX2u@j2)Q5+c z?{m!)wDqqkG%xPW5l}I};*fTp(2Eok2XEy+i@stnCmSKre1(sp&2wi7ask2svA$z; ze&n3h^Ect~(EWBKdNAp3np|4osoMOa#JzFiVy^eYJ4>o_M$kgF59VXTH?!ZqNIP&R zEwVn4<52d^>q)px<%lrP)g^@%H-(_jyP*ekV{Q@Xh91z3xkaEGdO$aJymwkCQ`79n zSP}O?<>o7p|5p{nEYWPMWFw6aF>wgLOswhI=rM}kIsk_lCg3BU7lSB#^u zL-+77Xc7gWkLS*p0vd?5x4-8OtOHmChI{Tfz><>CiX|O%SBhq{KU}t9KU%o)Z$eZ8 pU_LQ&F@X8lF4``hn@@WeU_R>H>VWxV_H~_sn>bb^Br2&c{12`hrJn!* literal 0 HcmV?d00001 diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/organization/part-00001-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/organization/part-00001-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz new file mode 100644 index 0000000000000000000000000000000000000000..137790bdec7f86831a611bc4dec1e7cc8e42fa41 GIT binary patch literal 3428 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 literal 0 HcmV?d00001 diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/organization/part-00002-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/organization/part-00002-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz new file mode 100644 index 0000000000000000000000000000000000000000..6b090b9f57974446bdef603e7cd1c3b14256b36e GIT binary patch literal 3191 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 literal 0 HcmV?d00001 diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/relation/part-00000-94553c9f-4ae6-4db9-919d-85ddc0a60f92-c000.txt.gz b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/relation/part-00000-94553c9f-4ae6-4db9-919d-85ddc0a60f92-c000.txt.gz new file mode 100644 index 0000000000000000000000000000000000000000..080665d22d16b6550043d98dfab96209683890c0 GIT binary patch literal 683 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 Date: Mon, 29 Mar 2021 16:59:16 +0200 Subject: [PATCH 188/445] [OpenOrgsWf] graph construction wf: allow to skip the import openorgs node (importOpenorgs true|false) --- .../dhp/oa/graph/raw_all/oozie_app/workflow.xml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_all/oozie_app/workflow.xml b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_all/oozie_app/workflow.xml index 389a889cb..ea12171e9 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_all/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_all/oozie_app/workflow.xml @@ -10,6 +10,11 @@ false should import content from the aggregator or reuse a previous version + + importOpenorgs + true + should import content from the OpenOrgs database + contentPath path location to store (or reuse) content from the aggregator @@ -202,10 +207,18 @@ --dbschema${dbSchema} --nsPrefixBlacklist${nsPrefixBlacklist} - + + + + ${wf:conf('importOpenorgs') eq true} + ${wf:conf('importOpenorgs') eq false} + + + + From 9237d55d7f0e9eba6b251139297fdcb500023c8b Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 29 Mar 2021 17:40:34 +0200 Subject: [PATCH 189/445] [OpenOrgsWf] cleanup --- .../oa/dedup/openorgs/oozie_app/workflow.xml | 4 -- .../raw_organizations/oozie_app/workflow.xml | 53 +------------------ 2 files changed, 1 insertion(+), 56 deletions(-) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml index dc63d0a79..d00ebad6c 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml @@ -16,10 +16,6 @@ workingPath path for the working directory - - dedupGraphPath - path for the output graph - cutConnectedComponent max number of elements in a connected component diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_organizations/oozie_app/workflow.xml b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_organizations/oozie_app/workflow.xml index 714d69697..fb6e02555 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_organizations/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_organizations/oozie_app/workflow.xml @@ -214,57 +214,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file From 27681b876c1194c0e3bb6bc76ed7989c5445ec2b Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 29 Mar 2021 17:47:11 +0200 Subject: [PATCH 190/445] code formatting --- .../dhp/schema/oaf/CleaningFunctions.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index 401d5d444..8da0a35e4 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -152,22 +152,23 @@ public class CleaningFunctions { Optional .ofNullable(i.getPid()) .ifPresent(pid -> { - final Set pids = - pid + final Set pids = pid + .stream() + .filter(Objects::nonNull) + .filter(p -> StringUtils.isNotBlank(p.getValue())) + .collect(Collectors.toCollection(HashSet::new)); + + Optional + .ofNullable(i.getAlternateIdentifier()) + .ifPresent(altId -> { + final Set altIds = altId .stream() .filter(Objects::nonNull) .filter(p -> StringUtils.isNotBlank(p.getValue())) .collect(Collectors.toCollection(HashSet::new)); - Optional.ofNullable(i.getAlternateIdentifier()) - .ifPresent(altId -> { - final Set altIds = altId.stream() - .filter(Objects::nonNull) - .filter(p -> StringUtils.isNotBlank(p.getValue())) - .collect(Collectors.toCollection(HashSet::new)); - - i.setAlternateIdentifier(Lists.newArrayList(Sets.difference(altIds, pids))); - }); + i.setAlternateIdentifier(Lists.newArrayList(Sets.difference(altIds, pids))); + }); }); if (Objects.isNull(i.getAccessright()) || StringUtils.isBlank(i.getAccessright().getClassid())) { From 616d2ecce2b104a9a3a9fe7d9ed813fbfb40d6db Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Wed, 31 Mar 2021 15:45:58 +0200 Subject: [PATCH 191/445] splitted workflow collecting datacite into two workflows. Released on beta --- .../dhp/schema/common/ModelConstants.java | 7 ++ .../DataciteToOAFTransformation.scala | 64 ++++++++-------- .../GenerateDataciteDatasetSpark.scala | 1 - .../datacite/ImportDatacite.scala | 2 +- .../actionset/oozie_app/config-default.xml | 23 ++++++ .../datacite/actionset/oozie_app/workflow.xml | 46 +++++++++++ .../datacite/generate_dataset_params.json | 6 -- .../datacite/oozie_app/workflow.xml | 76 +++---------------- .../dhp/transformation/oozie_app/workflow.xml | 2 +- .../datacite/DataciteToOAFTest.scala | 35 +++++++++ .../dhp/actionmanager/datacite/record.json | 1 + .../mag/SparkImportMagIntoDataset.scala | 5 +- 12 files changed, 160 insertions(+), 108 deletions(-) create mode 100644 dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/actionset/oozie_app/config-default.xml create mode 100644 dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/actionset/oozie_app/workflow.xml create mode 100644 dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTest.scala create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/actionmanager/datacite/record.json diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java index eaa8acef5..2ed672c12 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java @@ -89,6 +89,13 @@ public class ModelConstants { public static final String UNKNOWN = "UNKNOWN"; public static final String NOT_AVAILABLE = "not available"; + public static final String ACTION_SET_SCHEME = "sysimport:actionset"; + + public static final String PROVENANCE_VOCABULARY = "dnet:provenanceActions"; + + public static final Qualifier ACTION_SET_PROVENANCE_QUALIFIER = qualifier( + ACTION_SET_SCHEME, ACTION_SET_SCHEME, PROVENANCE_VOCABULARY, PROVENANCE_VOCABULARY); + public static final Qualifier PUBLICATION_DEFAULT_RESULTTYPE = qualifier( PUBLICATION_RESULTTYPE_CLASSID, PUBLICATION_RESULTTYPE_CLASSID, DNET_RESULT_TYPOLOGIES, DNET_RESULT_TYPOLOGIES); diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala index 1776a4ad6..1f1abba60 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala @@ -3,7 +3,9 @@ package eu.dnetlib.dhp.actionmanager.datacite import com.fasterxml.jackson.databind.ObjectMapper import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup import eu.dnetlib.dhp.schema.action.AtomicAction -import eu.dnetlib.dhp.schema.oaf.{Author, DataInfo, Instance, KeyValue, Oaf, OafMapperUtils, OtherResearchProduct, Publication, Qualifier, Relation, Result, Software, StructuredProperty, Dataset => OafDataset} +import eu.dnetlib.dhp.schema.common.ModelConstants +import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory +import eu.dnetlib.dhp.schema.oaf.{AccessRight, Author, DataInfo, Instance, KeyValue, Oaf, OafMapperUtils, OtherResearchProduct, Publication, Qualifier, Relation, Result, Software, StructuredProperty, Dataset => OafDataset} import eu.dnetlib.dhp.utils.DHPUtils import org.apache.commons.lang3.StringUtils import org.json4s.DefaultFormats @@ -45,17 +47,11 @@ object DataciteToOAFTransformation { codec.onMalformedInput(CodingErrorAction.REPLACE) codec.onUnmappableCharacter(CodingErrorAction.REPLACE) - - - private val PID_VOCABULARY = "dnet:pid_types" - val COBJ_VOCABULARY = "dnet:publication_resource" - val RESULT_VOCABULARY = "dnet:result_typologies" - val ACCESS_MODE_VOCABULARY = "dnet:access_modes" val DOI_CLASS = "doi" - val TITLE_SCHEME = "dnet:dataCite_title" + val SUBJ_CLASS = "keywords" - val SUBJ_SCHEME = "dnet:subject_classification_typologies" + val j_filter:List[String] = { val s = Source.fromInputStream(getClass.getResourceAsStream("datacite_filter")).mkString @@ -66,7 +62,7 @@ object DataciteToOAFTransformation { val unknown_repository: HostedByMapType = HostedByMapType("openaire____::1256f046-bf1f-4afc-8b47-d0b147148b18", "Unknown Repository", "Unknown Repository", Some(1.0F)) val dataInfo: DataInfo = generateDataInfo("0.9") - val DATACITE_COLLECTED_FROM: KeyValue = OafMapperUtils.keyValue("openaire____::9e3be59865b2c1c335d32dae2fe7b254", "Datacite") + val DATACITE_COLLECTED_FROM: KeyValue = OafMapperUtils.keyValue(ModelConstants.DATACITE_ID, "Datacite") val hostedByMap: Map[String, HostedByMapType] = { val s = Source.fromInputStream(getClass.getResourceAsStream("hostedBy_map.json")).mkString @@ -174,20 +170,20 @@ object DataciteToOAFTransformation { def getTypeQualifier(resourceType: String, resourceTypeGeneral: String, schemaOrg: String, vocabularies:VocabularyGroup): (Qualifier, Qualifier) = { if (resourceType != null && resourceType.nonEmpty) { - val typeQualifier = vocabularies.getSynonymAsQualifier(COBJ_VOCABULARY, resourceType) + val typeQualifier = vocabularies.getSynonymAsQualifier(ModelConstants.DNET_PUBLICATION_RESOURCE, resourceType) if (typeQualifier != null) - return (typeQualifier, vocabularies.getSynonymAsQualifier(RESULT_VOCABULARY, typeQualifier.getClassid)) + return (typeQualifier, vocabularies.getSynonymAsQualifier(ModelConstants.DNET_RESULT_TYPOLOGIES, typeQualifier.getClassid)) } if (schemaOrg != null && schemaOrg.nonEmpty) { - val typeQualifier = vocabularies.getSynonymAsQualifier(COBJ_VOCABULARY, schemaOrg) + val typeQualifier = vocabularies.getSynonymAsQualifier(ModelConstants.DNET_PUBLICATION_RESOURCE, schemaOrg) if (typeQualifier != null) - return (typeQualifier, vocabularies.getSynonymAsQualifier(RESULT_VOCABULARY, typeQualifier.getClassid)) + return (typeQualifier, vocabularies.getSynonymAsQualifier(ModelConstants.DNET_RESULT_TYPOLOGIES, typeQualifier.getClassid)) } if (resourceTypeGeneral != null && resourceTypeGeneral.nonEmpty) { - val typeQualifier = vocabularies.getSynonymAsQualifier(COBJ_VOCABULARY, resourceTypeGeneral) + val typeQualifier = vocabularies.getSynonymAsQualifier(ModelConstants.DNET_PUBLICATION_RESOURCE, resourceTypeGeneral) if (typeQualifier != null) - return (typeQualifier, vocabularies.getSynonymAsQualifier(RESULT_VOCABULARY, typeQualifier.getClassid)) + return (typeQualifier, vocabularies.getSynonymAsQualifier(ModelConstants.DNET_RESULT_TYPOLOGIES, typeQualifier.getClassid)) } null @@ -295,7 +291,7 @@ object DataciteToOAFTransformation { return List() - val doi_q = vocabularies.getSynonymAsQualifier(PID_VOCABULARY, "doi") + val doi_q = OafMapperUtils.qualifier("doi", "doi", ModelConstants.DNET_PID_TYPES,ModelConstants.DNET_PID_TYPES) val pid = OafMapperUtils.structuredProperty(doi, doi_q, dataInfo) result.setPid(List(pid).asJava) result.setId(OafMapperUtils.createOpenaireId(50, s"datacite____::$doi", true)) @@ -319,7 +315,7 @@ object DataciteToOAFTransformation { a.setSurname(c.familyName.orNull) if (c.nameIdentifiers!= null&& c.nameIdentifiers.isDefined && c.nameIdentifiers.get != null) { a.setPid(c.nameIdentifiers.get.map(ni => { - val q = if (ni.nameIdentifierScheme.isDefined) vocabularies.getTermAsQualifier(PID_VOCABULARY, ni.nameIdentifierScheme.get.toLowerCase()) else null + val q = if (ni.nameIdentifierScheme.isDefined) vocabularies.getTermAsQualifier(ModelConstants.DNET_PID_TYPES, ni.nameIdentifierScheme.get.toLowerCase()) else null if (ni.nameIdentifier!= null && ni.nameIdentifier.isDefined) { OafMapperUtils.structuredProperty(ni.nameIdentifier.get, q, dataInfo) } @@ -343,9 +339,9 @@ object DataciteToOAFTransformation { result.setTitle(titles.filter(t => t.title.nonEmpty).map(t => { if (t.titleType.isEmpty) { - OafMapperUtils.structuredProperty(t.title.get, "main title", "main title", TITLE_SCHEME, TITLE_SCHEME, null) + OafMapperUtils.structuredProperty(t.title.get, ModelConstants.MAIN_TITLE_QUALIFIER, null) } else { - OafMapperUtils.structuredProperty(t.title.get, t.titleType.get, t.titleType.get, TITLE_SCHEME, TITLE_SCHEME, null) + OafMapperUtils.structuredProperty(t.title.get, t.titleType.get, t.titleType.get, ModelConstants.DNET_DATACITE_TITLE, ModelConstants.DNET_DATACITE_TITLE, null) } }).asJava) @@ -390,7 +386,7 @@ object DataciteToOAFTransformation { result.setSubject(subjects.filter(s => s.subject.nonEmpty) .map(s => - OafMapperUtils.structuredProperty(s.subject.get, SUBJ_CLASS, SUBJ_CLASS, SUBJ_SCHEME, SUBJ_SCHEME, null) + OafMapperUtils.structuredProperty(s.subject.get, SUBJ_CLASS, SUBJ_CLASS, ModelConstants.DNET_SUBJECT_TYPOLOGIES, ModelConstants.DNET_SUBJECT_TYPOLOGIES, null) ).asJava) @@ -426,28 +422,33 @@ object DataciteToOAFTransformation { JField("rightsUri", JString(rightsUri)) <- rightsList } yield rightsUri - val aRights: Option[Qualifier] = accessRights.map(r => { - vocabularies.getSynonymAsQualifier(ACCESS_MODE_VOCABULARY, r) - }).find(q => q != null) + val aRights: Option[AccessRight] = accessRights.map(r => { + vocabularies.getSynonymAsQualifier(ModelConstants.DNET_ACCESS_MODES, r) + }).find(q => q != null).map(q => { + val a = new AccessRight + a.setClassid(q.getClassid) + a.setClassname(q.getClassname) + a.setSchemeid(q.getSchemeid) + a.setSchemename(q.getSchemename) + a + }) - val access_rights_qualifier = if (aRights.isDefined) aRights.get else OafMapperUtils.qualifier("UNKNOWN", "not available", ACCESS_MODE_VOCABULARY, ACCESS_MODE_VOCABULARY) + val access_rights_qualifier = if (aRights.isDefined) aRights.get else OafMapperUtils.accessRight("UNKNOWN", "not available", ModelConstants.DNET_ACCESS_MODES, ModelConstants.DNET_ACCESS_MODES) if (client.isDefined) { val hb = hostedByMap.getOrElse(client.get.toUpperCase(), unknown_repository) instance.setHostedby(OafMapperUtils.keyValue(generateDSId(hb.openaire_id), hb.official_name)) instance.setCollectedfrom(DATACITE_COLLECTED_FROM) instance.setUrl(List(s"https://dx.doi.org/$doi").asJava) -// instance.setAccessright(access_rights_qualifier) - - //'http') and matches(., '.*(/licenses|/publicdomain|unlicense.org/|/legal-and-data-protection-notices|/download/license|/open-government-licence).*')]"> + instance.setAccessright(access_rights_qualifier) + instance.setPid(result.getPid) val license = accessRights .find(r => r.startsWith("http") && r.matches(".*(/licenses|/publicdomain|unlicense\\.org/|/legal-and-data-protection-notices|/download/license|/open-government-licence).*")) if (license.isDefined) instance.setLicense(OafMapperUtils.field(license.get, null)) } - val awardUris:List[String] = for { JObject(fundingReferences) <- json \\ "fundingReferences" JField("awardUri", JString(awardUri)) <- fundingReferences @@ -455,6 +456,9 @@ object DataciteToOAFTransformation { val relations:List[Relation] =awardUris.flatMap(a=> get_projectRelation(a, result.getId)).filter(r => r!= null) + result.setId(IdentifierFactory.createIdentifier(result)) + if(result.getId == null) + return List() if (relations!= null && relations.nonEmpty) { List(result):::relations } @@ -468,7 +472,7 @@ object DataciteToOAFTransformation { di.setInferred(false) di.setInvisible(false) di.setTrust(trust) - di.setProvenanceaction(OafMapperUtils.qualifier("sysimport:actionset", "sysimport:actionset", "dnet:provenanceActions", "dnet:provenanceActions")) + di.setProvenanceaction(ModelConstants.ACTION_SET_PROVENANCE_QUALIFIER) di } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/GenerateDataciteDatasetSpark.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/GenerateDataciteDatasetSpark.scala index 168ad218a..44b175cb2 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/GenerateDataciteDatasetSpark.scala +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/GenerateDataciteDatasetSpark.scala @@ -27,7 +27,6 @@ object GenerateDataciteDatasetSpark { val isLookupService = ISLookupClientFactory.getLookUpService(isLookupUrl) val vocabularies = VocabularyGroup.loadVocsFromIS(isLookupService) - log.info(s"vocabulary size is ${vocabularies.getTerms("dnet:languages").size()}") val spark: SparkSession = SparkSession.builder().config(conf) .appName(GenerateDataciteDatasetSpark.getClass.getSimpleName) .master(master) diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ImportDatacite.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ImportDatacite.scala index 6cec4ea34..8e9e8728e 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ImportDatacite.scala +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/ImportDatacite.scala @@ -148,7 +148,7 @@ object ImportDatacite { try { var start: Long = System.currentTimeMillis while (from < now) { - client = new DataciteAPIImporter(from, 1000, from + delta) + client = new DataciteAPIImporter(from, 100, from + delta) var end: Long = 0 val key: IntWritable = new IntWritable(i) val value: Text = new Text diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/actionset/oozie_app/config-default.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/actionset/oozie_app/config-default.xml new file mode 100644 index 000000000..dd3c32c62 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/actionset/oozie_app/config-default.xml @@ -0,0 +1,23 @@ + + + jobTracker + yarnRM + + + nameNode + hdfs://nameservice1 + + + oozie.use.system.libpath + true + + + oozie.action.sharelib.for.spark + spark2 + + + + oozie.launcher.mapreduce.user.classpath.first + true + + \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/actionset/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/actionset/oozie_app/workflow.xml new file mode 100644 index 000000000..3c58ace7b --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/actionset/oozie_app/workflow.xml @@ -0,0 +1,46 @@ + + + + sourcePath + the working path of Datacite stores + + + outputPath + the path of Datacite ActionSet + + + + + + + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] + + + + + + yarn-cluster + cluster + ExportDataset + eu.dnetlib.dhp.actionmanager.datacite.ExportActionSetJobNode + dhp-aggregation-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --executor-cores=${sparkExecutorCores} + --driver-memory=${sparkDriverMemory} + --conf spark.sql.shuffle.partitions=3840 + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + + --sourcePath${sourcePath} + --targetPath${outputPath} + --masteryarn-cluster + + + + + + + \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/generate_dataset_params.json b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/generate_dataset_params.json index 34fa3ed99..dea037fd4 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/generate_dataset_params.json +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/generate_dataset_params.json @@ -12,12 +12,6 @@ "paramDescription": "the target mdstore path", "paramRequired": true }, - { - "paramName": "tr", - "paramLongName": "transformationRule", - "paramDescription": "the transformation Rule", - "paramRequired": true - }, { "paramName": "m", "paramLongName": "master", diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/workflow.xml index 15378c6c7..3eee2e5a8 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/actionmanager/datacite/oozie_app/workflow.xml @@ -1,41 +1,21 @@ - mdstoreInputPath - the path of the input MDStore - - - - mdstoreOutputPath - the path of the cleaned mdstore + mainPath + the working path of Datacite stores - nativeInputPath - the path of the input MDStore + isLookupUrl + The IS lookUp service endopoint - - skipimport - false - the path of the input MDStore - - - - - + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] - - - ${wf:conf('resumeFrom') eq 'TransformJob'} - ${wf:conf('resumeFrom') eq 'ExportDataset'} - - - @@ -53,10 +33,9 @@ --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} - -t${nativeInputPath} - -d${mdstoreInputPath} - -n${nameNode} - -s${skipimport} + --targetPath${mainPath}/datacite_update + --dataciteDumpPath${mainPath}/datacite_dump + --namenode${nameNode} --masteryarn-cluster @@ -81,44 +60,9 @@ --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} - --sourcePath${mdstoreInputPath} - --targetPath${mdstoreOutputPath} + --sourcePath${mainPath}/datacite_dump + --targetPath${mainPath}/datacite_oaf --isLookupUrl${isLookupUrl} - -tr${isLookupUrl} - --masteryarn-cluster - - - - - - - - - - - - - - - - - yarn-cluster - cluster - ExportDataset - eu.dnetlib.dhp.actionmanager.datacite.ExportActionSetJobNode - dhp-aggregation-${projectVersion}.jar - - --executor-memory=${sparkExecutorMemory} - --executor-cores=${sparkExecutorCores} - --driver-memory=${sparkDriverMemory} - --conf spark.sql.shuffle.partitions=3840 - --conf spark.extraListeners=${spark2ExtraListeners} - --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} - --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} - --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} - - --sourcePath${mdstoreOutputPath} - --targetPath${mdstoreOutputPath}_raw_AS --masteryarn-cluster diff --git a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml index 61e5710fa..3d7b1bf22 100644 --- a/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-aggregation/src/main/resources/eu/dnetlib/dhp/transformation/oozie_app/workflow.xml @@ -1,4 +1,4 @@ - + mdStoreInputId diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTest.scala b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTest.scala new file mode 100644 index 000000000..a7d404300 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTest.scala @@ -0,0 +1,35 @@ +package eu.dnetlib.dhp.actionmanager.datacite + +import com.fasterxml.jackson.databind.ObjectMapper +import eu.dnetlib.dhp.aggregation.AbstractVocabularyTest +import eu.dnetlib.dhp.schema.oaf.Oaf +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.{BeforeEach, Test} +import org.mockito.junit.jupiter.MockitoExtension + +import scala.io.Source + +@ExtendWith(Array(classOf[MockitoExtension])) +class DataciteToOAFTest extends AbstractVocabularyTest{ + + + @BeforeEach + def setUp() :Unit = { + println("Called Method") + super.setUpVocabulary() + } + + @Test + def testMapping() :Unit = { + val record =Source.fromInputStream(getClass.getResourceAsStream("record.json")).mkString + + + + val mapper = new ObjectMapper() + val res:List[Oaf] =DataciteToOAFTransformation.generateOAF(record, 0L,0L, vocabularies ) + println (mapper.writeValueAsString(res.head)) + + + } + +} \ No newline at end of file diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/actionmanager/datacite/record.json b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/actionmanager/datacite/record.json new file mode 100644 index 000000000..b5ac40e25 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/actionmanager/datacite/record.json @@ -0,0 +1 @@ +{"relationships": {"client": {"data": {"type": "clients", "id": "gbif.gbif"}}}, "attributes": {"partCount": 0, "contributors": [], "versionCount": 0, "titles": [{"title": "Occurrence Download"}], "descriptions": [{"lang": "eng", "descriptionType": "Abstract", "description": "A dataset containing 2452 species occurrences available in GBIF matching the query: { \"TaxonKey\" : [ \"is Indotyphlops braminus (Daudin, 1803)\" ] } The dataset includes 2452 records from 48 constituent datasets: 8 records from India Biodiversity Portal publication grade dataset. 6 records from UCM Amphibian and Reptile Collection (Arctos). 577 records from iNaturalist Research-grade Observations. 5 records from NCSM Herpetology Collection. 4 records from Vertebrate Zoology Division - Herpetology, Yale Peabody Museum. 4 records from Asian Herpetological Collection - IICT. 11 records from WildNet - Queensland Wildlife Data. 68 records from Australian Museum provider for OZCAM. 33 records from Geographically tagged INSDC sequences. 8 records from LACM Vertebrate Collection. 19 records from Herpetology. 88 records from KUBI Herpetology Collection. 187 records from The reptiles and amphibians collection (RA) of the Mus\u00e9um national d'Histoire Naturelle (MNHN - Paris). 36 records from Queensland Museum provider for OZCAM. 46 records from DAYO: Invasive Alien Reptiles in the Philippines. 33 records from MVZ Herp Collection (Arctos). 22 records from BYU Herpetology Collection. 1 records from Atlas des amphibiens et reptiles de Martinique. 2 records from University of Alberta Museum of Zoology Amphibian and Reptile Collection (UAMZ). 2 records from NMNH Paleobiology Specimen Records. 23 records from South Australian Museum Australia provider for OZCAM. 30 records from Fauna Atlas N.T.. 302 records from Museum of Comparative Zoology, Harvard University. 9 records from Herpetology Collection NRM. 397 records from University of Florida Herpetology. 40 records from Western Australian Museum provider for OZCAM. 1 records from Collection Herpetology SMF. 16 records from TNHC Herpetology Collection. 28 records from International Barcode of Life project (iBOL). 26 records from Base de donn\u00e9es de NOI \u2013 JDD_HISTORIQUE. 24 records from Naturalis Biodiversity Center (NL) - Amphibia and Reptilia. 242 records from Field Museum of Natural History (Zoology) Amphibian and Reptile Collection. 18 records from Australian National Wildlife Collection provider for OZCAM. 1 records from Herpetology. 1 records from Donn\u00e9es naturalistes d'Olivier ESCUDER. 12 records from Reptile Specimens. 1 records from Lund Museum of Zoology (MZLU). 2 records from Donn\u00e9es d'occurrences Esp\u00e8ces issues de l'inventaire des ZNIEFF. 6 records from Museums Victoria provider for OZCAM. 3 records from Questagame weekly feed. 78 records from Northern Territory Museum and Art Gallery provider for OZCAM. 1 records from SysTax - Zoological Collections. 11 records from UMZC Zoological Specimens. 1 records from AUMNH Herpetology Voucher Collection. 1 records from Tissues Specimens. 10 records from Apoyo a las colecciones biol\u00f3gicas de la Facultad de Ciencias de la UNAM: Fase 1 (MZFC_HE). 4 records from HerpMapper. 4 records from ALA species sightings and OzAtlas. Data from some individual datasets included in this download may be licensed under less restrictive terms."}], "referenceCount": 0, "subjects": [{"lang": "eng", "subject": "GBIF"}, {"lang": "eng", "subject": "biodiversity"}, {"lang": "eng", "subject": "species occurrences"}], "container": {}, "state": "findable", "created": "2020-08-23T05:03:37.000Z", "source": null, "metadataVersion": 0, "version": null, "isActive": true, "registered": "2020-08-23T05:03:37.000Z", "contentUrl": null, "geoLocations": [], "updated": "2020-08-23T05:03:37.000Z", "fundingReferences": [], "partOfCount": 0, "viewCount": 0, "versionOfCount": 0, "published": "2020", "dates": [{"date": "2020-08-23", "dateType": "Created"}, {"date": "2020-08-23", "dateType": "Updated"}, {"date": "2020", "dateType": "Issued"}], "relatedIdentifiers": [{"relationType": "References", "relatedIdentifier": "10.15468/rs5upd", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/1llmgl", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/ab3s5x", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/enivwl", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/ypdvp9", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/fhn7xo", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/lxgoyb", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/e7susi", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/cndomv", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/77rmwd", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/2wlj2m", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/ubdwdc", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/whdzq3", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/lotsye", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/cpv8vf", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/pi1mts", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/tekwqq", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/4eswn6", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.18165/qmltit", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/7m0fvd", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/wz4rrh", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/eeg0zb", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/p5rupv", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/b9o7h4", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/vw3dvj", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/5qt0dm", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/lkc3vq", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/xrorih", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/inygc6", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/lhcdhw", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/ythnjq", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/u2pzhj", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/gscnac", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/px1sya", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/g5giua", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/cplkwg", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/mw39rb", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/ikshke", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/lp1ctu", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/slqqt8", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/giro3a", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/zyqkbl", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/pjmjvn", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/d1vglq", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/5fmfwq", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/htjhrb", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/9tsf2l", "relatedIdentifierType": "DOI"}, {"relationType": "References", "relatedIdentifier": "10.15468/jayxmn", "relatedIdentifierType": "DOI"}], "reason": null, "rightsList": [{"rightsUri": "https://creativecommons.org/licenses/by-nc/4.0/legalcode", "rightsIdentifier": "cc-by-nc-4.0", "rightsIdentifierScheme": "SPDX", "schemeUri": "https://spdx.org/licenses/", "rights": "Creative Commons Attribution Non Commercial 4.0 International"}], "schemaVersion": "http://datacite.org/schema/kernel-4", "types": {"citeproc": "dataset", "resourceTypeGeneral": "Dataset", "schemaOrg": "Dataset", "bibtex": "misc", "ris": "DATA"}, "publisher": "The Global Biodiversity Information Facility", "publicationYear": 2020, "doi": "10.15468/dl.g9k8b9", "language": null, "sizes": ["161836"], "url": "https://www.gbif.org/occurrence/download/0044053-200613084148143", "identifiers": [], "citationCount": 0, "formats": ["Darwin Core Archive"], "downloadCount": 0, "creators": [{"nameType": "Organizational", "nameIdentifiers": [], "name": "Occdownload Gbif.Org", "affiliation": []}]}, "type": "dois", "id": "10.15468/dl.g9k8b9", "timestamp": 1598151817} \ No newline at end of file diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/SparkImportMagIntoDataset.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/SparkImportMagIntoDataset.scala index 88fee72b7..76d29206a 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/SparkImportMagIntoDataset.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/SparkImportMagIntoDataset.scala @@ -6,7 +6,6 @@ import org.apache.spark.SparkConf import org.apache.spark.sql.{SaveMode, SparkSession} import org.apache.spark.sql.types._ import org.slf4j.{Logger, LoggerFactory} -import org.apache.spark.sql.functions._ object SparkImportMagIntoDataset { val datatypedict = Map( @@ -25,11 +24,10 @@ object SparkImportMagIntoDataset { "AuthorExtendedAttributes" -> Tuple2("mag/AuthorExtendedAttributes.txt", Seq("AuthorId:long", "AttributeType:int", "AttributeValue:string")), "Authors" -> Tuple2("mag/Authors.txt", Seq("AuthorId:long", "Rank:uint", "NormalizedName:string", "DisplayName:string", "LastKnownAffiliationId:long?", "PaperCount:long", "PaperFamilyCount:long", "CitationCount:long", "CreatedDate:DateTime")), "ConferenceInstances" -> Tuple2("mag/ConferenceInstances.txt", Seq("ConferenceInstanceId:long", "NormalizedName:string", "DisplayName:string", "ConferenceSeriesId:long", "Location:string", "OfficialUrl:string", "StartDate:DateTime?", "EndDate:DateTime?", "AbstractRegistrationDate:DateTime?", "SubmissionDeadlineDate:DateTime?", "NotificationDueDate:DateTime?", "FinalVersionDueDate:DateTime?", "PaperCount:long", "PaperFamilyCount:long" ,"CitationCount:long", "Latitude:float?", "Longitude:float?", "CreatedDate:DateTime")), - "ConferenceSeries" -> Tuple2("mag/ConferenceSeries.txt", Seq("ConferenceSeriesId:long", "Rank:uint", "NormalizedName:string", "DisplayName:string", "PaperCount:long", "CitationCount:long", "CreatedDate:DateTime")), + "ConferenceSeries" -> Tuple2("mag/ConferenceSeries.txt", Seq("ConferenceSeriesId:long", "Rank:uint", "NormalizedName:string", "DisplayName:string", "PaperCount:long", "PaperFamilyCount:long", "CitationCount:long", "CreatedDate:DateTime")), "EntityRelatedEntities" -> Tuple2("advanced/EntityRelatedEntities.txt", Seq("EntityId:long", "EntityType:string", "RelatedEntityId:long", "RelatedEntityType:string", "RelatedType:int", "Score:float")), "FieldOfStudyChildren" -> Tuple2("advanced/FieldOfStudyChildren.txt", Seq("FieldOfStudyId:long", "ChildFieldOfStudyId:long")), "FieldOfStudyExtendedAttributes" -> Tuple2("advanced/FieldOfStudyExtendedAttributes.txt", Seq("FieldOfStudyId:long", "AttributeType:int", "AttributeValue:string")), - // ['FieldOfStudyId:long', 'Rank:uint', 'NormalizedName:string', 'DisplayName:string', 'MainType:string', 'Level:int', 'PaperCount:long', 'PaperFamilyCount:long', 'CitationCount:long', 'CreatedDate:DateTime'] "FieldsOfStudy" -> Tuple2("advanced/FieldsOfStudy.txt", Seq("FieldOfStudyId:long", "Rank:uint", "NormalizedName:string", "DisplayName:string", "MainType:string", "Level:int", "PaperCount:long", "PaperFamilyCount:long", "CitationCount:long", "CreatedDate:DateTime")), "Journals" -> Tuple2("mag/Journals.txt", Seq("JournalId:long", "Rank:uint", "NormalizedName:string", "DisplayName:string", "Issn:string", "Publisher:string", "Webpage:string", "PaperCount:long", "PaperFamilyCount:long" ,"CitationCount:long", "CreatedDate:DateTime")), "PaperAbstractsInvertedIndex" -> Tuple2("nlp/PaperAbstractsInvertedIndex.txt.*", Seq("PaperId:long", "IndexedAbstract:string")), @@ -37,6 +35,7 @@ object SparkImportMagIntoDataset { "PaperCitationContexts" -> Tuple2("nlp/PaperCitationContexts.txt", Seq("PaperId:long", "PaperReferenceId:long", "CitationContext:string")), "PaperExtendedAttributes" -> Tuple2("mag/PaperExtendedAttributes.txt", Seq("PaperId:long", "AttributeType:int", "AttributeValue:string")), "PaperFieldsOfStudy" -> Tuple2("advanced/PaperFieldsOfStudy.txt", Seq("PaperId:long", "FieldOfStudyId:long", "Score:float")), + "PaperMeSH" -> Tuple2("advanced/PaperMeSH.txt", Seq("PaperId:long", "DescriptorUI:string", "DescriptorName:string", "QualifierUI:string", "QualifierName:string", "IsMajorTopic:bool")), "PaperRecommendations" -> Tuple2("advanced/PaperRecommendations.txt", Seq("PaperId:long", "RecommendedPaperId:long", "Score:float")), "PaperReferences" -> Tuple2("mag/PaperReferences.txt", Seq("PaperId:long", "PaperReferenceId:long")), "PaperResources" -> Tuple2("mag/PaperResources.txt", Seq("PaperId:long", "ResourceType:int", "ResourceUrl:string", "SourceUrl:string", "RelationshipType:int")), From 72ce741ea6e9c163e541d1a390aac185715e5f83 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 31 Mar 2021 17:07:13 +0200 Subject: [PATCH 192/445] WIP: using common definitions from ModelConstants --- .../dhp/schema/oaf/CleaningFunctions.java | 8 +- .../dhp/schema/common/ModelConstants.java | 15 +- .../dhp/schema/common/ModelSupport.java | 154 +++++++++--------- .../dhp/schema/action/AtomicActionTest.java | 7 +- .../DataciteToOAFTransformation.scala | 26 ++- .../oa/dedup/SparkCopyOpenorgsMergeRels.java | 12 +- .../oa/dedup/SparkCopyOpenorgsSimRels.java | 16 +- .../dhp/oa/dedup/SparkCreateMergeRels.java | 7 +- .../dhp/oa/dedup/SparkPrepareNewOrgs.java | 11 +- .../dhp/oa/dedup/SparkPrepareOrgRels.java | 27 +-- .../dhp/oa/dedup/SparkPropagateRelation.java | 7 +- .../raw/MigrateDbEntitiesApplication.java | 8 +- .../dhp/oa/graph/raw/OdfToOafMapper.java | 3 +- .../java/eu/dnetlib/dhp/export/DLIToOAF.scala | 43 ++--- .../dhp/oa/provision/RelationComparator.java | 23 +-- .../dhp/oa/provision/SortableRelation.java | 23 +-- .../provision/model/SortableRelationKey.java | 23 +-- .../oa/provision/utils/XmlRecordFactory.java | 3 +- 18 files changed, 219 insertions(+), 197 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index 8da0a35e4..b9d89a82b 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -174,7 +174,9 @@ public class CleaningFunctions { if (Objects.isNull(i.getAccessright()) || StringUtils.isBlank(i.getAccessright().getClassid())) { i .setAccessright( - accessRight(ModelConstants.UNKNOWN, "not available", ModelConstants.DNET_ACCESS_MODES)); + accessRight( + ModelConstants.UNKNOWN, ModelConstants.NOT_AVAILABLE, + ModelConstants.DNET_ACCESS_MODES)); } if (Objects.isNull(i.getHostedby()) || StringUtils.isBlank(i.getHostedby().getKey())) { i.setHostedby(ModelConstants.UNKNOWN_REPOSITORY); @@ -189,7 +191,9 @@ public class CleaningFunctions { if (Objects.isNull(bestaccessrights)) { r .setBestaccessright( - qualifier("UNKNOWN", "not available", ModelConstants.DNET_ACCESS_MODES)); + qualifier( + ModelConstants.UNKNOWN, ModelConstants.NOT_AVAILABLE, + ModelConstants.DNET_ACCESS_MODES)); } else { r.setBestaccessright(bestaccessrights); } diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java index eaa8acef5..fb02a5e00 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java @@ -18,6 +18,8 @@ public class ModelConstants { public static final String PUBMED_CENTRAL_ID = "10|opendoar____::eda80a3d5b344bc40f3bc04f65b7a357"; public static final String ARXIV_ID = "10|opendoar____::6f4922f45568161a8cdf4ad2299f6d23"; + public static final String OPENORGS_NAME = "OpenOrgs Database"; + // VOCABULARY VALUE public static final String ACCESS_RIGHT_OPEN = "OPEN"; @@ -55,12 +57,12 @@ public class ModelConstants { public static final String IS_SUPPLEMENTED_BY = "isSupplementedBy"; public static final String PART = "part"; public static final String IS_PART_OF = "isPartOf"; - public static final String HAS_PARTS = "hasParts"; + public static final String HAS_PART = "hasPart"; public static final String RELATIONSHIP = "relationship"; public static final String CITATION = "citation"; public static final String CITES = "cites"; public static final String IS_CITED_BY = "isCitedBy"; - public static final String REVIEW = "review"; + public static final String REVIEW = "review"; // subreltype public static final String REVIEWS = "reviews"; public static final String IS_REVIEWED_BY = "isReviewedBy"; @@ -84,7 +86,16 @@ public class ModelConstants { public static final String IS_AUTHOR_INSTITUTION_OF = "isAuthorInstitutionOf"; public static final String HAS_AUTHOR_INSTITUTION = "hasAuthorInstitution"; + public static final String ORG_ORG_RELTYPE = "organizationOrganization"; + + public static final String DEDUP = "dedup"; public static final String MERGES = "merges"; + public static final String IS_MERGED_IN = "isMergedIn"; + + public static final String SIMILARITY = "similarity"; + public static final String IS_SIMILAR_TO = "isSimilarTo"; + + public static final String IS_DIFFERENT_FROM = "isDifferentFrom"; public static final String UNKNOWN = "UNKNOWN"; public static final String NOT_AVAILABLE = "not available"; diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java index 85f24bc0b..b6a2015ed 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java @@ -116,45 +116,45 @@ public class ModelSupport { relationInverseMap .put( "projectOrganization_participation_isParticipant", new RelationInverse() - .setRelation("isParticipant") - .setInverse("hasParticipant") - .setRelType("projectOrganization") - .setSubReltype("participation")); + .setRelation(ModelConstants.IS_PARTICIPANT) + .setInverse(ModelConstants.HAS_PARTICIPANT) + .setRelType(ModelConstants.PROJECT_ORGANIZATION) + .setSubReltype(ModelConstants.PARTICIPATION)); relationInverseMap .put( "projectOrganization_participation_hasParticipant", new RelationInverse() - .setInverse("isParticipant") - .setRelation("hasParticipant") - .setRelType("projectOrganization") - .setSubReltype("participation")); + .setInverse(ModelConstants.IS_PARTICIPANT) + .setRelation(ModelConstants.HAS_PARTICIPANT) + .setRelType(ModelConstants.PROJECT_ORGANIZATION) + .setSubReltype(ModelConstants.PARTICIPATION)); relationInverseMap .put( "resultOrganization_affiliation_hasAuthorInstitution", new RelationInverse() - .setRelation("hasAuthorInstitution") - .setInverse("isAuthorInstitutionOf") - .setRelType("resultOrganization") - .setSubReltype("affiliation")); + .setRelation(ModelConstants.HAS_AUTHOR_INSTITUTION) + .setInverse(ModelConstants.IS_AUTHOR_INSTITUTION_OF) + .setRelType(ModelConstants.RESULT_ORGANIZATION) + .setSubReltype(ModelConstants.AFFILIATION)); relationInverseMap .put( "resultOrganization_affiliation_isAuthorInstitutionOf", new RelationInverse() - .setInverse("hasAuthorInstitution") - .setRelation("isAuthorInstitutionOf") - .setRelType("resultOrganization") - .setSubReltype("affiliation")); + .setInverse(ModelConstants.HAS_AUTHOR_INSTITUTION) + .setRelation(ModelConstants.IS_AUTHOR_INSTITUTION_OF) + .setRelType(ModelConstants.RESULT_ORGANIZATION) + .setSubReltype(ModelConstants.AFFILIATION)); relationInverseMap .put( "organizationOrganization_dedup_merges", new RelationInverse() - .setRelation("merges") - .setInverse("isMergedIn") - .setRelType("organizationOrganization") - .setSubReltype("dedup")); + .setRelation(ModelConstants.MERGES) + .setInverse(ModelConstants.IS_MERGED_IN) + .setRelType(ModelConstants.ORG_ORG_RELTYPE) + .setSubReltype(ModelConstants.DEDUP)); relationInverseMap .put( "organizationOrganization_dedup_isMergedIn", new RelationInverse() - .setInverse("merges") - .setRelation("isMergedIn") - .setRelType("organizationOrganization") - .setSubReltype("dedup")); + .setInverse(ModelConstants.MERGES) + .setRelation(ModelConstants.IS_MERGED_IN) + .setRelType(ModelConstants.ORG_ORG_RELTYPE) + .setSubReltype(ModelConstants.DEDUP)); relationInverseMap .put( "organizationOrganization_dedupSimilarity_isSimilarTo", new RelationInverse() @@ -166,17 +166,17 @@ public class ModelSupport { relationInverseMap .put( "resultProject_outcome_isProducedBy", new RelationInverse() - .setRelation("isProducedBy") - .setInverse("produces") - .setRelType("resultProject") - .setSubReltype("outcome")); + .setRelation(ModelConstants.IS_PRODUCED_BY) + .setInverse(ModelConstants.PRODUCES) + .setRelType(ModelConstants.RESULT_PROJECT) + .setSubReltype(ModelConstants.OUTCOME)); relationInverseMap .put( "resultProject_outcome_produces", new RelationInverse() - .setInverse("isProducedBy") - .setRelation("produces") - .setRelType("resultProject") - .setSubReltype("outcome")); + .setInverse(ModelConstants.IS_PRODUCED_BY) + .setRelation(ModelConstants.PRODUCES) + .setRelType(ModelConstants.RESULT_PROJECT) + .setSubReltype(ModelConstants.OUTCOME)); relationInverseMap .put( "projectPerson_contactPerson_isContact", new RelationInverse() @@ -201,17 +201,17 @@ public class ModelSupport { relationInverseMap .put( "personPerson_dedup_merges", new RelationInverse() - .setInverse("isMergedIn") - .setRelation("merges") + .setInverse(ModelConstants.IS_MERGED_IN) + .setRelation(ModelConstants.MERGES) .setRelType("personPerson") - .setSubReltype("dedup")); + .setSubReltype(ModelConstants.DEDUP)); relationInverseMap .put( "personPerson_dedup_isMergedIn", new RelationInverse() - .setInverse("merges") - .setRelation("isMergedIn") + .setInverse(ModelConstants.MERGES) + .setRelation(ModelConstants.IS_MERGED_IN) .setRelType("personPerson") - .setSubReltype("dedup")); + .setSubReltype(ModelConstants.DEDUP)); relationInverseMap .put( "personPerson_dedupSimilarity_isSimilarTo", new RelationInverse() @@ -222,86 +222,86 @@ public class ModelSupport { relationInverseMap .put( "datasourceOrganization_provision_isProvidedBy", new RelationInverse() - .setInverse("provides") - .setRelation("isProvidedBy") - .setRelType("datasourceOrganization") - .setSubReltype("provision")); + .setInverse(ModelConstants.PROVIDES) + .setRelation(ModelConstants.IS_PROVIDED_BY) + .setRelType(ModelConstants.DATASOURCE_ORGANIZATION) + .setSubReltype(ModelConstants.PROVISION)); relationInverseMap .put( "datasourceOrganization_provision_provides", new RelationInverse() - .setInverse("isProvidedBy") - .setRelation("provides") - .setRelType("datasourceOrganization") - .setSubReltype("provision")); + .setInverse(ModelConstants.IS_PROVIDED_BY) + .setRelation(ModelConstants.PROVIDES) + .setRelType(ModelConstants.DATASOURCE_ORGANIZATION) + .setSubReltype(ModelConstants.PROVISION)); relationInverseMap .put( "resultResult_similarity_hasAmongTopNSimilarDocuments", new RelationInverse() .setInverse("isAmongTopNSimilarDocuments") .setRelation("hasAmongTopNSimilarDocuments") - .setRelType("resultResult") + .setRelType(ModelConstants.RESULT_RESULT) .setSubReltype("similarity")); relationInverseMap .put( "resultResult_similarity_isAmongTopNSimilarDocuments", new RelationInverse() .setInverse("hasAmongTopNSimilarDocuments") .setRelation("isAmongTopNSimilarDocuments") - .setRelType("resultResult") + .setRelType(ModelConstants.RESULT_RESULT) .setSubReltype("similarity")); relationInverseMap .put( "resultResult_relationship_isRelatedTo", new RelationInverse() - .setInverse("isRelatedTo") - .setRelation("isRelatedTo") - .setRelType("resultResult") - .setSubReltype("relationship")); + .setInverse(ModelConstants.IS_RELATED_TO) + .setRelation(ModelConstants.IS_RELATED_TO) + .setRelType(ModelConstants.RESULT_RESULT) + .setSubReltype(ModelConstants.RELATIONSHIP)); relationInverseMap .put( "resultResult_supplement_isSupplementTo", new RelationInverse() - .setInverse("isSupplementedBy") - .setRelation("isSupplementTo") - .setRelType("resultResult") - .setSubReltype("supplement")); + .setInverse(ModelConstants.IS_SUPPLEMENTED_BY) + .setRelation(ModelConstants.IS_SUPPLEMENT_TO) + .setRelType(ModelConstants.RESULT_RESULT) + .setSubReltype(ModelConstants.SUPPLEMENT)); relationInverseMap .put( "resultResult_supplement_isSupplementedBy", new RelationInverse() - .setInverse("isSupplementTo") - .setRelation("isSupplementedBy") - .setRelType("resultResult") - .setSubReltype("supplement")); + .setInverse(ModelConstants.IS_SUPPLEMENT_TO) + .setRelation(ModelConstants.IS_SUPPLEMENTED_BY) + .setRelType(ModelConstants.RESULT_RESULT) + .setSubReltype(ModelConstants.SUPPLEMENT)); relationInverseMap .put( "resultResult_part_isPartOf", new RelationInverse() - .setInverse("hasPart") - .setRelation("isPartOf") - .setRelType("resultResult") - .setSubReltype("part")); + .setInverse(ModelConstants.HAS_PART) + .setRelation(ModelConstants.IS_PART_OF) + .setRelType(ModelConstants.RESULT_RESULT) + .setSubReltype(ModelConstants.PART)); relationInverseMap .put( "resultResult_part_hasPart", new RelationInverse() - .setInverse("isPartOf") - .setRelation("hasPart") - .setRelType("resultResult") - .setSubReltype("part")); + .setInverse(ModelConstants.IS_PART_OF) + .setRelation(ModelConstants.HAS_PART) + .setRelType(ModelConstants.RESULT_RESULT) + .setSubReltype(ModelConstants.PART)); relationInverseMap .put( "resultResult_dedup_merges", new RelationInverse() - .setInverse("isMergedIn") - .setRelation("merges") - .setRelType("resultResult") - .setSubReltype("dedup")); + .setInverse(ModelConstants.IS_MERGED_IN) + .setRelation(ModelConstants.MERGES) + .setRelType(ModelConstants.RESULT_RESULT) + .setSubReltype(ModelConstants.DEDUP)); relationInverseMap .put( "resultResult_dedup_isMergedIn", new RelationInverse() - .setInverse("merges") - .setRelation("isMergedIn") - .setRelType("resultResult") - .setSubReltype("dedup")); + .setInverse(ModelConstants.MERGES) + .setRelation(ModelConstants.IS_MERGED_IN) + .setRelType(ModelConstants.RESULT_RESULT) + .setSubReltype(ModelConstants.DEDUP)); relationInverseMap .put( "resultResult_dedupSimilarity_isSimilarTo", new RelationInverse() .setInverse("isSimilarTo") .setRelation("isSimilarTo") - .setRelType("resultResult") + .setRelType(ModelConstants.RESULT_RESULT) .setSubReltype("dedupSimilarity")); } diff --git a/dhp-schemas/src/test/java/eu/dnetlib/dhp/schema/action/AtomicActionTest.java b/dhp-schemas/src/test/java/eu/dnetlib/dhp/schema/action/AtomicActionTest.java index 4d31591a0..9818d03e7 100644 --- a/dhp-schemas/src/test/java/eu/dnetlib/dhp/schema/action/AtomicActionTest.java +++ b/dhp-schemas/src/test/java/eu/dnetlib/dhp/schema/action/AtomicActionTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.api.Test; import com.fasterxml.jackson.databind.ObjectMapper; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.Relation; /** @author claudio.atzori */ @@ -21,9 +22,9 @@ public class AtomicActionTest { Relation rel = new Relation(); rel.setSource("1"); rel.setTarget("2"); - rel.setRelType("resultResult"); - rel.setSubRelType("dedup"); - rel.setRelClass("merges"); + rel.setRelType(ModelConstants.RESULT_RESULT); + rel.setSubRelType(ModelConstants.DEDUP); + rel.setRelClass(ModelConstants.MERGES); AtomicAction aa1 = new AtomicAction(Relation.class, rel); diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala index 1776a4ad6..8c97dfd03 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala @@ -3,6 +3,7 @@ package eu.dnetlib.dhp.actionmanager.datacite import com.fasterxml.jackson.databind.ObjectMapper import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup import eu.dnetlib.dhp.schema.action.AtomicAction +import eu.dnetlib.dhp.schema.common.ModelConstants import eu.dnetlib.dhp.schema.oaf.{Author, DataInfo, Instance, KeyValue, Oaf, OafMapperUtils, OtherResearchProduct, Publication, Qualifier, Relation, Result, Software, StructuredProperty, Dataset => OafDataset} import eu.dnetlib.dhp.utils.DHPUtils import org.apache.commons.lang3.StringUtils @@ -45,11 +46,6 @@ object DataciteToOAFTransformation { codec.onMalformedInput(CodingErrorAction.REPLACE) codec.onUnmappableCharacter(CodingErrorAction.REPLACE) - - - private val PID_VOCABULARY = "dnet:pid_types" - val COBJ_VOCABULARY = "dnet:publication_resource" - val RESULT_VOCABULARY = "dnet:result_typologies" val ACCESS_MODE_VOCABULARY = "dnet:access_modes" val DOI_CLASS = "doi" @@ -174,20 +170,20 @@ object DataciteToOAFTransformation { def getTypeQualifier(resourceType: String, resourceTypeGeneral: String, schemaOrg: String, vocabularies:VocabularyGroup): (Qualifier, Qualifier) = { if (resourceType != null && resourceType.nonEmpty) { - val typeQualifier = vocabularies.getSynonymAsQualifier(COBJ_VOCABULARY, resourceType) + val typeQualifier = vocabularies.getSynonymAsQualifier(ModelConstants.DNET_PUBLICATION_RESOURCE, resourceType) if (typeQualifier != null) - return (typeQualifier, vocabularies.getSynonymAsQualifier(RESULT_VOCABULARY, typeQualifier.getClassid)) + return (typeQualifier, vocabularies.getSynonymAsQualifier(ModelConstants.DNET_RESULT_TYPOLOGIES, typeQualifier.getClassid)) } if (schemaOrg != null && schemaOrg.nonEmpty) { - val typeQualifier = vocabularies.getSynonymAsQualifier(COBJ_VOCABULARY, schemaOrg) + val typeQualifier = vocabularies.getSynonymAsQualifier(ModelConstants.DNET_PUBLICATION_RESOURCE, schemaOrg) if (typeQualifier != null) - return (typeQualifier, vocabularies.getSynonymAsQualifier(RESULT_VOCABULARY, typeQualifier.getClassid)) + return (typeQualifier, vocabularies.getSynonymAsQualifier(ModelConstants.DNET_RESULT_TYPOLOGIES, typeQualifier.getClassid)) } if (resourceTypeGeneral != null && resourceTypeGeneral.nonEmpty) { - val typeQualifier = vocabularies.getSynonymAsQualifier(COBJ_VOCABULARY, resourceTypeGeneral) + val typeQualifier = vocabularies.getSynonymAsQualifier(ModelConstants.DNET_PUBLICATION_RESOURCE, resourceTypeGeneral) if (typeQualifier != null) - return (typeQualifier, vocabularies.getSynonymAsQualifier(RESULT_VOCABULARY, typeQualifier.getClassid)) + return (typeQualifier, vocabularies.getSynonymAsQualifier(ModelConstants.DNET_RESULT_TYPOLOGIES, typeQualifier.getClassid)) } null @@ -295,7 +291,7 @@ object DataciteToOAFTransformation { return List() - val doi_q = vocabularies.getSynonymAsQualifier(PID_VOCABULARY, "doi") + val doi_q = vocabularies.getSynonymAsQualifier(ModelConstants.DNET_PID_TYPES, "doi") val pid = OafMapperUtils.structuredProperty(doi, doi_q, dataInfo) result.setPid(List(pid).asJava) result.setId(OafMapperUtils.createOpenaireId(50, s"datacite____::$doi", true)) @@ -319,7 +315,7 @@ object DataciteToOAFTransformation { a.setSurname(c.familyName.orNull) if (c.nameIdentifiers!= null&& c.nameIdentifiers.isDefined && c.nameIdentifiers.get != null) { a.setPid(c.nameIdentifiers.get.map(ni => { - val q = if (ni.nameIdentifierScheme.isDefined) vocabularies.getTermAsQualifier(PID_VOCABULARY, ni.nameIdentifierScheme.get.toLowerCase()) else null + val q = if (ni.nameIdentifierScheme.isDefined) vocabularies.getTermAsQualifier(ModelConstants.DNET_PID_TYPES, ni.nameIdentifierScheme.get.toLowerCase()) else null if (ni.nameIdentifier!= null && ni.nameIdentifier.isDefined) { OafMapperUtils.structuredProperty(ni.nameIdentifier.get, q, dataInfo) } @@ -427,11 +423,11 @@ object DataciteToOAFTransformation { } yield rightsUri val aRights: Option[Qualifier] = accessRights.map(r => { - vocabularies.getSynonymAsQualifier(ACCESS_MODE_VOCABULARY, r) + vocabularies.getSynonymAsQualifier(ModelConstants.DNET_ACCESS_MODES, r) }).find(q => q != null) - val access_rights_qualifier = if (aRights.isDefined) aRights.get else OafMapperUtils.qualifier("UNKNOWN", "not available", ACCESS_MODE_VOCABULARY, ACCESS_MODE_VOCABULARY) + val access_rights_qualifier = if (aRights.isDefined) aRights.get else OafMapperUtils.qualifier(ModelConstants.UNKNOWN, ModelConstants.NOT_AVAILABLE, ModelConstants.DNET_ACCESS_MODES, ModelConstants.DNET_ACCESS_MODES) if (client.isDefined) { val hb = hostedByMap.getOrElse(client.get.toUpperCase(), unknown_repository) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java index 6bd1a00b9..aa05ab65c 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java @@ -18,7 +18,10 @@ import org.dom4j.DocumentException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.sun.media.sound.ModelChannelMixer; + import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.DataInfo; import eu.dnetlib.dhp.schema.oaf.KeyValue; import eu.dnetlib.dhp.schema.oaf.Qualifier; @@ -135,8 +138,9 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { private boolean filterOpenorgsRels(Relation rel) { - if (rel.getRelClass().equals("isSimilarTo") && rel.getRelType().equals("organizationOrganization") - && rel.getSubRelType().equals("dedup")) + if (rel.getRelClass().equals(ModelConstants.IS_SIMILAR_TO) + && rel.getRelType().equals(ModelConstants.ORG_ORG_RELTYPE) + && rel.getSubRelType().equals(ModelConstants.DEDUP)) return true; return false; } @@ -145,7 +149,7 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { if (rel.getCollectedfrom() != null) { for (KeyValue k : rel.getCollectedfrom()) { - if (k.getValue() != null && k.getValue().equals("OpenOrgs Database")) { + if (k.getValue() != null && k.getValue().equals(ModelConstants.OPENORGS_NAME)) { return true; } } @@ -162,7 +166,7 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { r.setTarget(target); r.setRelClass(relClass); r.setRelType(entityType + entityType.substring(0, 1).toUpperCase() + entityType.substring(1)); - r.setSubRelType("dedup"); + r.setSubRelType(ModelConstants.DEDUP); DataInfo info = new DataInfo(); info.setDeletedbyinference(false); diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java index dbcd40289..91229fe53 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java @@ -2,17 +2,11 @@ package eu.dnetlib.dhp.oa.dedup; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; -import org.apache.spark.api.java.JavaRDD; -import org.apache.spark.api.java.function.ForeachFunction; import org.apache.spark.api.java.function.MapFunction; -import org.apache.spark.graphx.Edge; -import org.apache.spark.rdd.RDD; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoders; import org.apache.spark.sql.SaveMode; @@ -22,14 +16,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.DataInfo; import eu.dnetlib.dhp.schema.oaf.KeyValue; -import eu.dnetlib.dhp.schema.oaf.Qualifier; import eu.dnetlib.dhp.schema.oaf.Relation; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import eu.dnetlib.pace.config.DedupConfig; //copy simrels (verified) from relation to the workdir in order to make them available for the deduplication public class SparkCopyOpenorgsSimRels extends AbstractSparkAction { @@ -100,8 +93,9 @@ public class SparkCopyOpenorgsSimRels extends AbstractSparkAction { private boolean filterOpenorgsRels(Relation rel) { - if (rel.getRelClass().equals("isSimilarTo") && rel.getRelType().equals("organizationOrganization") - && rel.getSubRelType().equals("dedup") && isOpenorgs(rel)) + if (rel.getRelClass().equals(ModelConstants.IS_SIMILAR_TO) + && rel.getRelType().equals(ModelConstants.ORG_ORG_RELTYPE) + && rel.getSubRelType().equals(ModelConstants.DEDUP) && isOpenorgs(rel)) return true; return false; } @@ -110,7 +104,7 @@ public class SparkCopyOpenorgsSimRels extends AbstractSparkAction { if (rel.getCollectedfrom() != null) { for (KeyValue k : rel.getCollectedfrom()) { - if (k.getValue() != null && k.getValue().equals("OpenOrgs Database")) { + if (k.getValue() != null && k.getValue().equals(ModelConstants.OPENORGS_NAME)) { return true; } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateMergeRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateMergeRels.java index 1122b42eb..c27464f90 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateMergeRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateMergeRels.java @@ -28,6 +28,7 @@ import com.google.common.hash.Hashing; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.oa.dedup.graph.ConnectedComponent; import eu.dnetlib.dhp.oa.dedup.graph.GraphProcessor; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.schema.oaf.DataInfo; import eu.dnetlib.dhp.schema.oaf.Qualifier; @@ -135,8 +136,8 @@ public class SparkCreateMergeRels extends AbstractSparkAction { id -> { List tmp = new ArrayList<>(); - tmp.add(rel(cc.getCcId(), id, "merges", dedupConf)); - tmp.add(rel(id, cc.getCcId(), "isMergedIn", dedupConf)); + tmp.add(rel(cc.getCcId(), id, ModelConstants.MERGES, dedupConf)); + tmp.add(rel(id, cc.getCcId(), ModelConstants.IS_MERGED_IN, dedupConf)); return tmp.stream(); }) @@ -152,7 +153,7 @@ public class SparkCreateMergeRels extends AbstractSparkAction { r.setTarget(target); r.setRelClass(relClass); r.setRelType(entityType + entityType.substring(0, 1).toUpperCase() + entityType.substring(1)); - r.setSubRelType("dedup"); + r.setSubRelType(ModelConstants.DEDUP); DataInfo info = new DataInfo(); info.setDeletedbyinference(false); diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java index 950676677..ac51f3157 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java @@ -23,6 +23,7 @@ import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.oa.dedup.model.OrgSimRel; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.schema.oaf.DataInfo; import eu.dnetlib.dhp.schema.oaf.Organization; @@ -232,13 +233,15 @@ public class SparkPrepareNewOrgs extends AbstractSparkAction { switch (entityType) { case "result": - if (rel.getRelClass().equals("isDifferentFrom") && rel.getRelType().equals("resultResult") - && rel.getSubRelType().equals("dedup")) + if (rel.getRelClass().equals(ModelConstants.IS_DIFFERENT_FROM) + && rel.getRelType().equals(ModelConstants.RESULT_RESULT) + && rel.getSubRelType().equals(ModelConstants.DEDUP)) return true; break; case "organization": - if (rel.getRelClass().equals("isDifferentFrom") && rel.getRelType().equals("organizationOrganization") - && rel.getSubRelType().equals("dedup")) + if (rel.getRelClass().equals(ModelConstants.IS_DIFFERENT_FROM) + && rel.getRelType().equals(ModelConstants.ORG_ORG_RELTYPE) + && rel.getSubRelType().equals(ModelConstants.DEDUP)) return true; break; default: diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java index e2d9ae9c6..f1de3609b 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java @@ -22,6 +22,7 @@ import com.google.common.collect.Lists; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.oa.dedup.model.OrgSimRel; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.schema.oaf.DataInfo; import eu.dnetlib.dhp.schema.oaf.Organization; @@ -34,6 +35,8 @@ import scala.Tuple3; public class SparkPrepareOrgRels extends AbstractSparkAction { private static final Logger log = LoggerFactory.getLogger(SparkPrepareOrgRels.class); + public static final String OPENORGS_ID_PREFIX = "openorgs____"; + public static final String CORDA_ID_PREFIX = "corda"; public SparkPrepareOrgRels(ArgumentApplicationParser parser, SparkSession spark) { super(parser, spark); @@ -105,13 +108,15 @@ public class SparkPrepareOrgRels extends AbstractSparkAction { switch (entityType) { case "result": - if (rel.getRelClass().equals("isDifferentFrom") && rel.getRelType().equals("resultResult") - && rel.getSubRelType().equals("dedup")) + if (rel.getRelClass().equals(ModelConstants.IS_DIFFERENT_FROM) + && rel.getRelType().equals(ModelConstants.RESULT_RESULT) + && rel.getSubRelType().equals(ModelConstants.DEDUP)) return true; break; case "organization": - if (rel.getRelClass().equals("isDifferentFrom") && rel.getRelType().equals("organizationOrganization") - && rel.getSubRelType().equals("dedup")) + if (rel.getRelClass().equals(ModelConstants.IS_DIFFERENT_FROM) + && rel.getRelType().equals(ModelConstants.ORG_ORG_RELTYPE) + && rel.getSubRelType().equals(ModelConstants.DEDUP)) return true; break; default: @@ -241,19 +246,19 @@ public class SparkPrepareOrgRels extends AbstractSparkAction { } public static int compareIds(String o1, String o2) { - if (o1.contains("openorgs____") && o2.contains("openorgs____")) + if (o1.contains(OPENORGS_ID_PREFIX) && o2.contains(OPENORGS_ID_PREFIX)) return o1.compareTo(o2); - if (o1.contains("corda") && o2.contains("corda")) + if (o1.contains(CORDA_ID_PREFIX) && o2.contains(CORDA_ID_PREFIX)) return o1.compareTo(o2); - if (o1.contains("openorgs____")) + if (o1.contains(OPENORGS_ID_PREFIX)) return -1; - if (o2.contains("openorgs____")) + if (o2.contains(OPENORGS_ID_PREFIX)) return 1; - if (o1.contains("corda")) + if (o1.contains(CORDA_ID_PREFIX)) return -1; - if (o2.contains("corda")) + if (o2.contains(CORDA_ID_PREFIX)) return 1; return o1.compareTo(o2); @@ -296,7 +301,7 @@ public class SparkPrepareOrgRels extends AbstractSparkAction { for (String id1 : g._2()) { for (String id2 : g._2()) { if (!id1.equals(id2)) - if (id1.contains("openorgs____") && !id2.contains("openorgsmesh")) + if (id1.contains(OPENORGS_ID_PREFIX) && !id2.contains("openorgsmesh")) rels.add(new Tuple2<>(id1, id2)); } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPropagateRelation.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPropagateRelation.java index 1cd1545cd..f69e9b5f3 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPropagateRelation.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPropagateRelation.java @@ -15,6 +15,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.schema.oaf.DataInfo; import eu.dnetlib.dhp.schema.oaf.Relation; @@ -72,7 +73,7 @@ public class SparkPropagateRelation extends AbstractSparkAction { .as(Encoders.bean(Relation.class)); Dataset> mergedIds = mergeRels - .where(col("relClass").equalTo("merges")) + .where(col("relClass").equalTo(ModelConstants.MERGES)) .select(col("source"), col("target")) .distinct() .map( @@ -202,7 +203,7 @@ public class SparkPropagateRelation extends AbstractSparkAction { } private static boolean containsDedup(final Relation r) { - return r.getSource().toLowerCase().contains("dedup") - || r.getTarget().toLowerCase().contains("dedup"); + return r.getSource().toLowerCase().contains(ModelConstants.DEDUP) + || r.getTarget().toLowerCase().contains(ModelConstants.DEDUP); } } diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java index 299ff7d78..19dcde3bd 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java @@ -59,6 +59,7 @@ import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; import eu.dnetlib.dhp.oa.graph.raw.common.AbstractMigrationApplication; import eu.dnetlib.dhp.oa.graph.raw.common.MigrateAction; import eu.dnetlib.dhp.oa.graph.raw.common.VerifyNsPrefixPredicate; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.Context; import eu.dnetlib.dhp.schema.oaf.DataInfo; import eu.dnetlib.dhp.schema.oaf.Dataset; @@ -85,9 +86,6 @@ public class MigrateDbEntitiesApplication extends AbstractMigrationApplication i public static final String SOURCE_TYPE = "source_type"; public static final String TARGET_TYPE = "target_type"; - private static final String ORG_ORG_RELTYPE = "organizationOrganization"; - private static final String ORG_ORG_SUBRELTYPE = "dedup"; - private final DbClient dbClient; private final long lastUpdateTimestamp; @@ -649,8 +647,8 @@ public class MigrateDbEntitiesApplication extends AbstractMigrationApplication i createOpenaireId(10, rs.getString("collectedfromid"), true), rs.getString("collectedfromname")); final Relation r1 = new Relation(); - r1.setRelType(ORG_ORG_RELTYPE); - r1.setSubRelType(ORG_ORG_SUBRELTYPE); + r1.setRelType(ModelConstants.ORG_ORG_RELTYPE); + r1.setSubRelType(ModelConstants.DEDUP); r1.setRelClass(relClass); r1.setSource(orgId1); r1.setTarget(orgId2); diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java index 21dc5eff6..7592ee525 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java @@ -5,7 +5,6 @@ import static eu.dnetlib.dhp.schema.common.ModelConstants.*; import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.*; import java.util.*; -import java.util.function.Function; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; @@ -352,7 +351,7 @@ public class OdfToOafMapper extends AbstractMdRecordToOafMapper { res .add( getRelation( - otherId, docId, RESULT_RESULT, PART, HAS_PARTS, entity)); + otherId, docId, RESULT_RESULT, PART, HAS_PART, entity)); } else { // TODO catch more semantics } diff --git a/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala b/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala index cad8d88d3..29d75cdbc 100644 --- a/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala +++ b/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala @@ -6,6 +6,7 @@ import java.time.LocalDateTime import java.time.format.DateTimeFormatter import eu.dnetlib.dhp.common.PacePerson import eu.dnetlib.dhp.schema.action.AtomicAction +import eu.dnetlib.dhp.schema.common.ModelConstants import eu.dnetlib.dhp.schema.oaf.{Author, Dataset, ExternalReference, Field, Instance, KeyValue, Oaf, Publication, Qualifier, Relation, Result, StructuredProperty} import eu.dnetlib.dhp.schema.scholexplorer.{DLIDataset, DLIPublication} import eu.dnetlib.dhp.utils.DHPUtils @@ -43,18 +44,18 @@ object DLIToOAF { val relationTypeMapping: Map[String, (String, String)] = Map( - "IsReferencedBy" -> ("isRelatedTo", "relationship"), - "References" -> ("isRelatedTo", "relationship"), - "IsRelatedTo" -> ("isRelatedTo", "relationship"), - "IsSupplementedBy" -> ("isSupplementedBy", "supplement"), - "Documents"-> ("isRelatedTo", "relationship"), - "Cites" -> ("cites", "citation"), - "Unknown" -> ("isRelatedTo", "relationship"), - "IsSourceOf" -> ("isRelatedTo", "relationship"), - "IsCitedBy" -> ("IsCitedBy", "citation"), - "Reviews" -> ("reviews", "review"), - "Describes" -> ("isRelatedTo", "relationship"), - "HasAssociationWith" -> ("isRelatedTo", "relationship") + "IsReferencedBy" -> (ModelConstants.IS_RELATED_TO, ModelConstants.RELATIONSHIP), + "References" -> (ModelConstants.IS_RELATED_TO, ModelConstants.RELATIONSHIP), + "IsRelatedTo" -> (ModelConstants.IS_RELATED_TO, ModelConstants.RELATIONSHIP), + "IsSupplementedBy" -> (ModelConstants.IS_SUPPLEMENTED_BY, ModelConstants.SUPPLEMENT), + "Documents" -> (ModelConstants.IS_RELATED_TO, ModelConstants.RELATIONSHIP), + "Cites" -> (ModelConstants.CITES, ModelConstants.CITATION), + "Unknown" -> (ModelConstants.IS_RELATED_TO, ModelConstants.RELATIONSHIP), + "IsSourceOf" -> (ModelConstants.IS_RELATED_TO, ModelConstants.RELATIONSHIP), + "IsCitedBy" -> (ModelConstants.IS_CITED_BY, ModelConstants.CITATION), + "Reviews" -> (ModelConstants.REVIEWS, ModelConstants.REVIEW), + "Describes" -> (ModelConstants.IS_RELATED_TO, ModelConstants.RELATIONSHIP), + "HasAssociationWith" -> (ModelConstants.IS_RELATED_TO, ModelConstants.RELATIONSHIP) ) val expectecdPidType = List("uniprot", "ena", "chembl", "ncbi-n", "ncbi-p", "genbank", "pdb", "url") @@ -279,7 +280,7 @@ object DLIToOAF { val rt = r.getRelType if (!relationTypeMapping.contains(rt)) return null - r.setRelType("resultResult") + r.setRelType(ModelConstants.RESULT_RESULT) r.setRelClass(relationTypeMapping(rt)._1) r.setSubRelType(relationTypeMapping(rt)._2) r.setSource(generateId(r.getSource)) @@ -316,7 +317,7 @@ object DLIToOAF { if (d.getAuthor == null || d.getAuthor.isEmpty) return null result.setAuthor(d.getAuthor.asScala.map(convertAuthor).asJava) - result.setResulttype(createQualifier(d.getResulttype.getClassid, d.getResulttype.getClassname, "dnet:result_typologies", "dnet:result_typologies")) + result.setResulttype(createQualifier(d.getResulttype.getClassid, d.getResulttype.getClassname, ModelConstants.DNET_RESULT_TYPOLOGIES, ModelConstants.DNET_RESULT_TYPOLOGIES)) if (d.getSubject != null) result.setSubject(d.getSubject.asScala.map(convertSubject).asJava) @@ -337,7 +338,7 @@ object DLIToOAF { result.setDateofacceptance(asField(d.getRelevantdate.get(0).getValue)) result.setPublisher(d.getPublisher) result.setSource(d.getSource) - result.setBestaccessright(createAccessRight("UNKNOWN", "not available", "dnet:access_modes", "dnet:access_modes")) + result.setBestaccessright(createAccessRight(ModelConstants.UNKNOWN, ModelConstants.NOT_AVAILABLE, ModelConstants.DNET_ACCESS_MODES, ModelConstants.DNET_ACCESS_MODES)) val instance_urls = if (fpids.head.length < 5) s"https://www.rcsb.org/structure/${fpids.head}" else s"https://dx.doi.org/${fpids.head}" @@ -367,13 +368,13 @@ object DLIToOAF { val i = new Instance i.setUrl(List(url).asJava) if (dataset) - i.setInstancetype(createQualifier("0021", "Dataset", "dnet:publication_resource", "dnet:publication_resource")) + i.setInstancetype(createQualifier("0021", "Dataset", ModelConstants.DNET_PUBLICATION_RESOURCE, ModelConstants.DNET_PUBLICATION_RESOURCE)) else - i.setInstancetype(createQualifier("0000", "Unknown", "dnet:publication_resource", "dnet:publication_resource")) + i.setInstancetype(createQualifier("0000", "Unknown", ModelConstants.DNET_PUBLICATION_RESOURCE, ModelConstants.DNET_PUBLICATION_RESOURCE)) if (originalInstance != null && originalInstance.getHostedby != null) i.setHostedby(originalInstance.getHostedby) - i.setAccessright(createAccessRight("UNKNOWN", "not available", "dnet:access_modes", "dnet:access_modes")) + i.setAccessright(createAccessRight(ModelConstants.UNKNOWN, ModelConstants.NOT_AVAILABLE, ModelConstants.DNET_ACCESS_MODES, ModelConstants.DNET_ACCESS_MODES)) i.setDateofacceptance(doa) i @@ -383,19 +384,19 @@ object DLIToOAF { def patchRelevantDate(d: StructuredProperty): StructuredProperty = { - d.setQualifier(createQualifier("UNKNOWN", "dnet:dataCite_date")) + d.setQualifier(createQualifier(ModelConstants.UNKNOWN, ModelConstants.DNET_DATACITE_DATE)) d } def patchTitle(t: StructuredProperty): StructuredProperty = { - t.setQualifier(createQualifier("main title", "dnet:dataCite_title")) + t.setQualifier(createQualifier("main title", ModelConstants.DNET_DATACITE_TITLE)) t } def convertSubject(s: StructuredProperty): StructuredProperty = { - s.setQualifier(createQualifier("keyword", "dnet:subject_classification_typologies")) + s.setQualifier(createQualifier("keyword", ModelConstants.DNET_SUBJECT_TYPOLOGIES)) s diff --git a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/RelationComparator.java b/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/RelationComparator.java index f2209c26c..84ee013aa 100644 --- a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/RelationComparator.java +++ b/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/RelationComparator.java @@ -8,6 +8,7 @@ import java.util.Optional; import com.google.common.collect.ComparisonChain; import com.google.common.collect.Maps; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.Relation; public class RelationComparator implements Comparator { @@ -15,18 +16,18 @@ public class RelationComparator implements Comparator { private static final Map weights = Maps.newHashMap(); static { - weights.put("outcome", 0); - weights.put("supplement", 1); - weights.put("review", 2); - weights.put("citation", 3); - weights.put("affiliation", 4); - weights.put("relationship", 5); - weights.put("publicationDataset", 6); - weights.put("similarity", 7); + weights.put(ModelConstants.OUTCOME, 0); + weights.put(ModelConstants.SUPPLEMENT, 1); + weights.put(ModelConstants.REVIEWS, 2); + weights.put(ModelConstants.CITATION, 3); + weights.put(ModelConstants.AFFILIATION, 4); + weights.put(ModelConstants.RELATIONSHIP, 5); + weights.put(ModelConstants.PUBLICATION_DATASET, 6); + weights.put(ModelConstants.SIMILARITY, 7); - weights.put("provision", 8); - weights.put("participation", 9); - weights.put("dedup", 10); + weights.put(ModelConstants.PROVISION, 8); + weights.put(ModelConstants.PARTICIPATION, 9); + weights.put(ModelConstants.DEDUP, 10); } private Integer getWeight(Relation o) { diff --git a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/SortableRelation.java b/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/SortableRelation.java index 8ce92a6a0..8740b47fc 100644 --- a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/SortableRelation.java +++ b/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/SortableRelation.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.google.common.collect.ComparisonChain; import com.google.common.collect.Maps; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.Relation; public class SortableRelation extends Relation implements Comparable, Serializable { @@ -16,18 +17,18 @@ public class SortableRelation extends Relation implements Comparable weights = Maps.newHashMap(); static { - weights.put("outcome", 0); - weights.put("supplement", 1); - weights.put("review", 2); - weights.put("citation", 3); - weights.put("affiliation", 4); - weights.put("relationship", 5); - weights.put("publicationDataset", 6); - weights.put("similarity", 7); + weights.put(ModelConstants.OUTCOME, 0); + weights.put(ModelConstants.SUPPLEMENT, 1); + weights.put(ModelConstants.REVIEW, 2); + weights.put(ModelConstants.CITATION, 3); + weights.put(ModelConstants.AFFILIATION, 4); + weights.put(ModelConstants.RELATIONSHIP, 5); + weights.put(ModelConstants.PUBLICATION_RESULTTYPE_CLASSID, 6); + weights.put(ModelConstants.SIMILARITY, 7); - weights.put("provision", 8); - weights.put("participation", 9); - weights.put("dedup", 10); + weights.put(ModelConstants.PROVISION, 8); + weights.put(ModelConstants.PARTICIPATION, 9); + weights.put(ModelConstants.DEDUP, 10); } private static final long serialVersionUID = 34753984579L; diff --git a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/model/SortableRelationKey.java b/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/model/SortableRelationKey.java index bd7b4d78e..463c15e9e 100644 --- a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/model/SortableRelationKey.java +++ b/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/model/SortableRelationKey.java @@ -9,6 +9,7 @@ import com.google.common.base.Objects; import com.google.common.collect.ComparisonChain; import com.google.common.collect.Maps; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.Relation; public class SortableRelationKey implements Comparable, Serializable { @@ -16,18 +17,18 @@ public class SortableRelationKey implements Comparable, Ser private static final Map weights = Maps.newHashMap(); static { - weights.put("participation", 0); + weights.put(ModelConstants.PARTICIPATION, 0); - weights.put("outcome", 1); - weights.put("affiliation", 2); - weights.put("dedup", 3); - weights.put("publicationDataset", 4); - weights.put("citation", 5); - weights.put("supplement", 6); - weights.put("review", 7); - weights.put("relationship", 8); - weights.put("provision", 9); - weights.put("similarity", 10); + weights.put(ModelConstants.OUTCOME, 1); + weights.put(ModelConstants.AFFILIATION, 2); + weights.put(ModelConstants.DEDUP, 3); + weights.put(ModelConstants.PUBLICATION_DATASET, 4); + weights.put(ModelConstants.CITATION, 5); + weights.put(ModelConstants.SUPPLEMENT, 6); + weights.put(ModelConstants.REVIEW, 7); + weights.put(ModelConstants.RELATIONSHIP, 8); + weights.put(ModelConstants.PROVISION, 9); + weights.put(ModelConstants.SIMILARITY, 10); } private static final long serialVersionUID = 3232323; diff --git a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/utils/XmlRecordFactory.java b/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/utils/XmlRecordFactory.java index af6081c5d..b9d450c75 100644 --- a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/utils/XmlRecordFactory.java +++ b/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/utils/XmlRecordFactory.java @@ -38,6 +38,7 @@ import com.mycila.xmltool.XMLTag; import eu.dnetlib.dhp.oa.provision.model.*; import eu.dnetlib.dhp.schema.common.EntityType; import eu.dnetlib.dhp.schema.common.MainEntityType; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.dhp.schema.oaf.Result; @@ -1226,7 +1227,7 @@ public class XmlRecordFactory implements Serializable { } private boolean isDuplicate(RelatedEntityWrapper link) { - return REL_SUBTYPE_DEDUP.equalsIgnoreCase(link.getRelation().getSubRelType()); + return ModelConstants.DEDUP.equalsIgnoreCase(link.getRelation().getSubRelType()); } private List listExtraInfo(OafEntity entity) { From 7941d7be294c02a513afc7b1690ac5b898676a78 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Wed, 31 Mar 2021 18:33:57 +0200 Subject: [PATCH 193/445] WIP: using common definitions from ModelConstants --- .../dhp/schema/common/ModelConstants.java | 20 ++++++++++------ .../dhp/schema/common/ModelSupport.java | 24 +++++++++---------- .../dhp/schema/scholexplorer/OafUtils.scala | 3 ++- .../dhp/schema/scholexplorer/DLItest.java | 7 +++--- .../migration/ProtoConverter.java | 5 ++-- .../DataciteToOAFTransformation.scala | 19 ++++++--------- .../dhp/broker/oa/util/BrokerConstants.java | 3 ++- .../oa/dedup/SparkCopyOpenorgsMergeRels.java | 16 ++++++------- .../dedup/SparkCreateConnectedComponent.java | 8 +++---- .../doiboost/DoiBoostMappingUtil.scala | 20 ++++------------ .../doiboost/crossref/Crossref2Oaf.scala | 10 ++++---- .../dnetlib/doiboost/mag/MagDataModel.scala | 17 ++++++------- .../dnetlib/doiboost/orcid/ORCIDToOAF.scala | 11 +++++---- .../orcidnodoi/oaf/PublicationToOaf.java | 15 ++++-------- .../dnetlib/doiboost/uw/UnpayWallToOAF.scala | 4 ++-- .../graph/merge/MergeGraphTableSparkJob.java | 3 ++- .../dhp/oa/graph/raw/OdfToOafMapper.java | 5 ++-- .../dhp/sx/ebi/SparkAddLinkUpdates.scala | 13 +++++----- .../parser/AbstractScholexplorerParser.java | 13 +++++----- .../parser/DatasetScholexplorerParser.java | 7 ++---- .../java/eu/dnetlib/dhp/export/DLIToOAF.scala | 8 +++---- .../dhp/oa/provision/RelationComparator.java | 2 +- 22 files changed, 112 insertions(+), 121 deletions(-) diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java index 364c36ee2..1f9a09c3a 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelConstants.java @@ -35,10 +35,22 @@ public class ModelConstants { public static final String DNET_PROVENANCE_ACTIONS = "dnet:provenanceActions"; public static final String DNET_COUNTRY_TYPE = "dnet:countries"; public static final String DNET_REVIEW_LEVELS = "dnet:review_levels"; + public static final String DNET_PROGRAMMING_LANGUAGES = "dnet:programming_languages"; + public static final String DNET_PROVENANCEACTIONS = "dnet:provenanceActions"; + public static final String DNET_EXTERNAL_REF_TYPES = "dnet:externalReference_typologies"; public static final String SYSIMPORT_CROSSWALK_REPOSITORY = "sysimport:crosswalk:repository"; public static final String SYSIMPORT_CROSSWALK_ENTITYREGISTRY = "sysimport:crosswalk:entityregistry"; + public static final String SYSIMPORT_ACTIONSET = "sysimport:actionset"; + public static final String SYSIMPORT_ORCID_NO_DOI = "sysimport:actionset:orcidworks-no-doi"; + public static final String USER_CLAIM = "user:claim"; + public static final String HARVESTED = "Harvested"; + + public static final String PROVENANCE_DEDUP = "sysimport:dedup"; + + public static final Qualifier PROVENANCE_ACTION_SET_QUALIFIER = qualifier( + SYSIMPORT_ACTIONSET, SYSIMPORT_ACTIONSET, DNET_PROVENANCEACTIONS, DNET_PROVENANCEACTIONS); public static final String DATASET_RESULTTYPE_CLASSID = "dataset"; public static final String PUBLICATION_RESULTTYPE_CLASSID = "publication"; @@ -100,13 +112,6 @@ public class ModelConstants { public static final String UNKNOWN = "UNKNOWN"; public static final String NOT_AVAILABLE = "not available"; - public static final String ACTION_SET_SCHEME = "sysimport:actionset"; - - public static final String PROVENANCE_VOCABULARY = "dnet:provenanceActions"; - - public static final Qualifier ACTION_SET_PROVENANCE_QUALIFIER = qualifier( - ACTION_SET_SCHEME, ACTION_SET_SCHEME, PROVENANCE_VOCABULARY, PROVENANCE_VOCABULARY); - public static final Qualifier PUBLICATION_DEFAULT_RESULTTYPE = qualifier( PUBLICATION_RESULTTYPE_CLASSID, PUBLICATION_RESULTTYPE_CLASSID, DNET_RESULT_TYPOLOGIES, DNET_RESULT_TYPOLOGIES); @@ -131,6 +136,7 @@ public class ModelConstants { SYSIMPORT_CROSSWALK_ENTITYREGISTRY, SYSIMPORT_CROSSWALK_ENTITYREGISTRY, DNET_PROVENANCE_ACTIONS, DNET_PROVENANCE_ACTIONS); + public static final String UNKNOWN_REPOSITORY_ORIGINALID = "openaire____::1256f046-bf1f-4afc-8b47-d0b147148b18"; public static final KeyValue UNKNOWN_REPOSITORY = keyValue( "10|openaire____::55045bd2a65019fd8e6741a755395c8c", "Unknown Repository"); diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java index b6a2015ed..18ce2d967 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/common/ModelSupport.java @@ -158,10 +158,10 @@ public class ModelSupport { relationInverseMap .put( "organizationOrganization_dedupSimilarity_isSimilarTo", new RelationInverse() - .setInverse("isSimilarTo") - .setRelation("isSimilarTo") - .setRelType("organizationOrganization") - .setSubReltype("dedupSimilarity")); + .setInverse(ModelConstants.IS_SIMILAR_TO) + .setRelation(ModelConstants.IS_SIMILAR_TO) + .setRelType(ModelConstants.ORG_ORG_RELTYPE) + .setSubReltype(ModelConstants.DEDUP)); relationInverseMap .put( @@ -215,10 +215,10 @@ public class ModelSupport { relationInverseMap .put( "personPerson_dedupSimilarity_isSimilarTo", new RelationInverse() - .setInverse("isSimilarTo") - .setRelation("isSimilarTo") + .setInverse(ModelConstants.IS_SIMILAR_TO) + .setRelation(ModelConstants.IS_SIMILAR_TO) .setRelType("personPerson") - .setSubReltype("dedupSimilarity")); + .setSubReltype(ModelConstants.DEDUP)); relationInverseMap .put( "datasourceOrganization_provision_isProvidedBy", new RelationInverse() @@ -239,14 +239,14 @@ public class ModelSupport { .setInverse("isAmongTopNSimilarDocuments") .setRelation("hasAmongTopNSimilarDocuments") .setRelType(ModelConstants.RESULT_RESULT) - .setSubReltype("similarity")); + .setSubReltype(ModelConstants.SIMILARITY)); relationInverseMap .put( "resultResult_similarity_isAmongTopNSimilarDocuments", new RelationInverse() .setInverse("hasAmongTopNSimilarDocuments") .setRelation("isAmongTopNSimilarDocuments") .setRelType(ModelConstants.RESULT_RESULT) - .setSubReltype("similarity")); + .setSubReltype(ModelConstants.SIMILARITY)); relationInverseMap .put( "resultResult_relationship_isRelatedTo", new RelationInverse() @@ -299,10 +299,10 @@ public class ModelSupport { relationInverseMap .put( "resultResult_dedupSimilarity_isSimilarTo", new RelationInverse() - .setInverse("isSimilarTo") - .setRelation("isSimilarTo") + .setInverse(ModelConstants.IS_SIMILAR_TO) + .setRelation(ModelConstants.IS_SIMILAR_TO) .setRelType(ModelConstants.RESULT_RESULT) - .setSubReltype("dedupSimilarity")); + .setSubReltype(ModelConstants.DEDUP)); } diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/scholexplorer/OafUtils.scala b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/scholexplorer/OafUtils.scala index defcb6a28..b0cd344be 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/scholexplorer/OafUtils.scala +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/scholexplorer/OafUtils.scala @@ -1,5 +1,6 @@ package eu.dnetlib.dhp.schema.scholexplorer +import eu.dnetlib.dhp.schema.common.ModelConstants import eu.dnetlib.dhp.schema.oaf.{AccessRight, DataInfo, Field, KeyValue, Qualifier, StructuredProperty} object OafUtils { @@ -21,7 +22,7 @@ object OafUtils { di.setInferred(false) di.setInvisible(invisible) di.setTrust(trust) - di.setProvenanceaction(createQualifier("sysimport:actionset", "dnet:provenanceActions")) + di.setProvenanceaction(createQualifier(ModelConstants.SYSIMPORT_ACTIONSET, ModelConstants.DNET_PROVENANCE_ACTIONS)) di } diff --git a/dhp-schemas/src/test/java/eu/dnetlib/dhp/schema/scholexplorer/DLItest.java b/dhp-schemas/src/test/java/eu/dnetlib/dhp/schema/scholexplorer/DLItest.java index e4596fcdd..16ce1523d 100644 --- a/dhp-schemas/src/test/java/eu/dnetlib/dhp/schema/scholexplorer/DLItest.java +++ b/dhp-schemas/src/test/java/eu/dnetlib/dhp/schema/scholexplorer/DLItest.java @@ -12,6 +12,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.Qualifier; import eu.dnetlib.dhp.schema.oaf.StructuredProperty; @@ -20,7 +21,7 @@ public class DLItest { @Test public void testMergePublication() throws JsonProcessingException { DLIPublication a1 = new DLIPublication(); - a1.setPid(Arrays.asList(createSP("123456", "pdb", "dnet:pid_types"))); + a1.setPid(Arrays.asList(createSP("123456", "pdb", ModelConstants.DNET_PID_TYPES))); a1.setTitle(Collections.singletonList(createSP("Un Titolo", "title", "dnetTitle"))); a1.setDlicollectedfrom(Arrays.asList(createCollectedFrom("znd", "Zenodo", "complete"))); a1.setCompletionStatus("complete"); @@ -30,8 +31,8 @@ public class DLItest { .setPid( Arrays .asList( - createSP("10.11", "doi", "dnet:pid_types"), - createSP("123456", "pdb", "dnet:pid_types"))); + createSP("10.11", "doi", ModelConstants.DNET_PID_TYPES), + createSP("123456", "pdb", ModelConstants.DNET_PID_TYPES))); a.setTitle(Collections.singletonList(createSP("A Title", "title", "dnetTitle"))); a .setDlicollectedfrom( diff --git a/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/migration/ProtoConverter.java b/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/migration/ProtoConverter.java index 5aeb38bb5..2478da0e9 100644 --- a/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/migration/ProtoConverter.java +++ b/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/migration/ProtoConverter.java @@ -15,6 +15,7 @@ import com.google.common.collect.Lists; import com.googlecode.protobuf.format.JsonFormat; import eu.dnetlib.data.proto.*; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.*; public class ProtoConverter implements Serializable { @@ -106,8 +107,8 @@ public class ProtoConverter implements Serializable { Qualifier q = new Qualifier(); q.setClassid(refereed.getValue()); q.setSchemename(refereed.getValue()); - q.setSchemeid("dnet:review_levels"); - q.setSchemename("dnet:review_levels"); + q.setSchemeid(DNET_REVIEW_LEVELS); + q.setSchemename(DNET_REVIEW_LEVELS); return q; } diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala index 871d5a5c3..6eaf2d377 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/actionmanager/datacite/DataciteToOAFTransformation.scala @@ -23,8 +23,6 @@ import java.util.regex.Pattern import scala.collection.JavaConverters._ import scala.io.{Codec, Source} - - case class DataciteType(doi:String,timestamp:Long,isActive:Boolean, json:String ){} case class NameIdentifiersType(nameIdentifierScheme: Option[String], schemeUri: Option[String], nameIdentifier: Option[String]) {} @@ -49,10 +47,7 @@ object DataciteToOAFTransformation { codec.onMalformedInput(CodingErrorAction.REPLACE) codec.onUnmappableCharacter(CodingErrorAction.REPLACE) - val ACCESS_MODE_VOCABULARY = "dnet:access_modes" val DOI_CLASS = "doi" - - val SUBJ_CLASS = "keywords" @@ -62,7 +57,7 @@ object DataciteToOAFTransformation { } val mapper = new ObjectMapper() - val unknown_repository: HostedByMapType = HostedByMapType("openaire____::1256f046-bf1f-4afc-8b47-d0b147148b18", "Unknown Repository", "Unknown Repository", Some(1.0F)) + val unknown_repository: HostedByMapType = HostedByMapType(ModelConstants.UNKNOWN_REPOSITORY_ORIGINALID, ModelConstants.UNKNOWN_REPOSITORY.getValue, ModelConstants.UNKNOWN_REPOSITORY.getValue, Some(1.0F)) val dataInfo: DataInfo = generateDataInfo("0.9") val DATACITE_COLLECTED_FROM: KeyValue = OafMapperUtils.keyValue(ModelConstants.DATACITE_ID, "Datacite") @@ -244,9 +239,9 @@ object DataciteToOAFTransformation { val r = new Relation r.setSource(sourceId) r.setTarget(targetId) - r.setRelType("resultProject") + r.setRelType(ModelConstants.RESULT_PROJECT) r.setRelClass(relClass) - r.setSubRelType("outcome") + r.setSubRelType(ModelConstants.OUTCOME) r.setCollectedfrom(List(cf).asJava) r.setDataInfo(di) r @@ -381,7 +376,7 @@ object DataciteToOAFTransformation { result.setRelevantdate(dates.filter(d => d.date.isDefined && d.dateType.isDefined) .map(d => (extract_date(d.date.get), d.dateType.get)) .filter(d => d._1.isDefined) - .map(d => (d._1.get, vocabularies.getTermAsQualifier("dnet:dataCite_date", d._2.toLowerCase()))) + .map(d => (d._1.get, vocabularies.getTermAsQualifier(ModelConstants.DNET_DATACITE_DATE, d._2.toLowerCase()))) .filter(d => d._2 != null) .map(d => generateOAFDate(d._1, d._2)).asJava) @@ -413,7 +408,7 @@ object DataciteToOAFTransformation { val language: String = (json \\ "language").extractOrElse[String](null) if (language != null) - result.setLanguage(vocabularies.getSynonymAsQualifier("dnet:languages", language)) + result.setLanguage(vocabularies.getSynonymAsQualifier(ModelConstants.DNET_LANGUAGES, language)) val instance = result.getInstance().get(0) @@ -437,7 +432,7 @@ object DataciteToOAFTransformation { }) - val access_rights_qualifier = if (aRights.isDefined) aRights.get else OafMapperUtils.accessRight("UNKNOWN", "not available", ModelConstants.DNET_ACCESS_MODES, ModelConstants.DNET_ACCESS_MODES) + val access_rights_qualifier = if (aRights.isDefined) aRights.get else OafMapperUtils.accessRight(ModelConstants.UNKNOWN, ModelConstants.NOT_AVAILABLE, ModelConstants.DNET_ACCESS_MODES, ModelConstants.DNET_ACCESS_MODES) if (client.isDefined) { val hb = hostedByMap.getOrElse(client.get.toUpperCase(), unknown_repository) @@ -475,7 +470,7 @@ object DataciteToOAFTransformation { di.setInferred(false) di.setInvisible(false) di.setTrust(trust) - di.setProvenanceaction(ModelConstants.ACTION_SET_PROVENANCE_QUALIFIER) + di.setProvenanceaction(ModelConstants.PROVENANCE_ACTION_SET_QUALIFIER) di } diff --git a/dhp-workflows/dhp-broker-events/src/main/java/eu/dnetlib/dhp/broker/oa/util/BrokerConstants.java b/dhp-workflows/dhp-broker-events/src/main/java/eu/dnetlib/dhp/broker/oa/util/BrokerConstants.java index 7a09862d8..2055a014e 100644 --- a/dhp-workflows/dhp-broker-events/src/main/java/eu/dnetlib/dhp/broker/oa/util/BrokerConstants.java +++ b/dhp-workflows/dhp-broker-events/src/main/java/eu/dnetlib/dhp/broker/oa/util/BrokerConstants.java @@ -7,12 +7,13 @@ import java.util.Set; import eu.dnetlib.dhp.broker.model.Event; import eu.dnetlib.dhp.broker.oa.util.aggregators.simple.ResultGroup; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.common.ModelSupport; public class BrokerConstants { public static final String OPEN_ACCESS = "OPEN"; - public static final String IS_MERGED_IN_CLASS = "isMergedIn"; + public static final String IS_MERGED_IN_CLASS = ModelConstants.IS_MERGED_IN; public static final String COLLECTED_FROM_REL = "collectedFrom"; diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java index aa05ab65c..00036ebb0 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java @@ -35,8 +35,6 @@ import net.sf.saxon.ma.trie.Tuple2; public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { private static final Logger log = LoggerFactory.getLogger(SparkCopyOpenorgsMergeRels.class); - public static final String PROVENANCE_ACTION_CLASS = "sysimport:dedup"; - public static final String DNET_PROVENANCE_ACTIONS = "dnet:provenanceActions"; public SparkCopyOpenorgsMergeRels(ArgumentApplicationParser parser, SparkSession spark) { super(parser, spark); @@ -93,7 +91,7 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { JavaRDD selfRawRels = rawRels .map(r -> r.getSource()) .distinct() - .map(s -> rel(s, s, "isSimilarTo", dedupConf)); + .map(s -> rel(s, s, ModelConstants.IS_SIMILAR_TO, dedupConf)); log.info("Number of raw Openorgs Relations collected: {}", rawRels.count()); @@ -109,8 +107,8 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { List mergerels = new ArrayList<>(); - mergerels.add(rel(rel.getSource(), rel.getTarget(), "merges", dedupConf)); - mergerels.add(rel(rel.getTarget(), rel.getSource(), "isMergedIn", dedupConf)); + mergerels.add(rel(rel.getSource(), rel.getTarget(), ModelConstants.MERGES, dedupConf)); + mergerels.add(rel(rel.getTarget(), rel.getSource(), ModelConstants.IS_MERGED_IN, dedupConf)); return mergerels.iterator(); }); @@ -174,10 +172,10 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { info.setInvisible(false); info.setInferenceprovenance(dedupConf.getWf().getConfigurationId()); Qualifier provenanceAction = new Qualifier(); - provenanceAction.setClassid(PROVENANCE_ACTION_CLASS); - provenanceAction.setClassname(PROVENANCE_ACTION_CLASS); - provenanceAction.setSchemeid(DNET_PROVENANCE_ACTIONS); - provenanceAction.setSchemename(DNET_PROVENANCE_ACTIONS); + provenanceAction.setClassid(ModelConstants.PROVENANCE_DEDUP); + provenanceAction.setClassname(ModelConstants.PROVENANCE_DEDUP); + provenanceAction.setSchemeid(ModelConstants.DNET_PROVENANCE_ACTIONS); + provenanceAction.setSchemename(ModelConstants.DNET_PROVENANCE_ACTIONS); info.setProvenanceaction(provenanceAction); // TODO calculate the trust value based on the similarity score of the elements in the CC diff --git a/dhp-workflows/dhp-dedup-scholexplorer/src/main/java/eu/dnetlib/dedup/SparkCreateConnectedComponent.java b/dhp-workflows/dhp-dedup-scholexplorer/src/main/java/eu/dnetlib/dedup/SparkCreateConnectedComponent.java index 9bc90d51d..2f0b1e574 100644 --- a/dhp-workflows/dhp-dedup-scholexplorer/src/main/java/eu/dnetlib/dedup/SparkCreateConnectedComponent.java +++ b/dhp-workflows/dhp-dedup-scholexplorer/src/main/java/eu/dnetlib/dedup/SparkCreateConnectedComponent.java @@ -22,6 +22,7 @@ import com.google.common.hash.Hashing; import eu.dnetlib.dedup.graph.ConnectedComponent; import eu.dnetlib.dedup.graph.GraphProcessor; import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.Oaf; import eu.dnetlib.dhp.schema.oaf.Relation; import eu.dnetlib.pace.config.DedupConfig; @@ -47,8 +48,7 @@ public class SparkCreateConnectedComponent { final String inputPath = parser.get("sourcePath"); final String entity = parser.get("entity"); final String targetPath = parser.get("targetPath"); - // final DedupConfig dedupConf = - // DedupConfig.load(IOUtils.toString(SparkCreateConnectedComponent.class.getResourceAsStream("/eu/dnetlib/dhp/dedup/conf/org.curr.conf2.json"))); + final DedupConfig dedupConf = DedupConfig.load(parser.get("dedupConf")); final JavaPairRDD vertexes = spark @@ -88,12 +88,12 @@ public class SparkCreateConnectedComponent { Relation r = new Relation(); r.setSource(c.getCcId()); r.setTarget(id); - r.setRelClass("merges"); + r.setRelClass(ModelConstants.MERGES); tmp.add(r); r = new Relation(); r.setTarget(c.getCcId()); r.setSource(id); - r.setRelClass("isMergedIn"); + r.setRelClass(ModelConstants.IS_MERGED_IN); tmp.add(r); return tmp.stream(); }) diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala index f2c63cee6..a6101c07e 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/DoiBoostMappingUtil.scala @@ -30,8 +30,6 @@ object DoiBoostMappingUtil { //STATIC STRING val MAG = "microsoft" val MAG_NAME = "Microsoft Academic Graph" - val ORCID = "orcid" - val ORCID_PENDING = "orcid_pending" val CROSSREF = "Crossref" val UNPAYWALL = "UnpayWall" val GRID_AC = "grid.ac" @@ -39,8 +37,6 @@ object DoiBoostMappingUtil { val doiBoostNSPREFIX = "doiboost____" val OPENAIRE_PREFIX = "openaire____" val SEPARATOR = "::" - val DNET_LANGUAGES = "dnet:languages" - val PID_TYPES = "dnet:pid_types" val invalidName = List(",", "none none", "none, none", "none &na;", "(:null)", "test test test", "test test", "test", "&na; &na;") @@ -122,12 +118,11 @@ object DoiBoostMappingUtil { def getOpenAccessQualifier():AccessRight = { - OafUtils.createAccessRight("OPEN","Open Access","dnet:access_modes", "dnet:access_modes") + OafUtils.createAccessRight("OPEN","Open Access", ModelConstants.DNET_ACCESS_MODES, ModelConstants.DNET_ACCESS_MODES) } def getRestrictedQualifier():AccessRight = { - OafUtils.createAccessRight("RESTRICTED","Restricted","dnet:access_modes", "dnet:access_modes") - + OafUtils.createAccessRight("RESTRICTED","Restricted",ModelConstants.DNET_ACCESS_MODES, ModelConstants.DNET_ACCESS_MODES) } @@ -141,14 +136,12 @@ object DoiBoostMappingUtil { val item = if (input._2 != null) input._2._2 else null - val instanceType:Option[Instance] = extractInstance(publication) if (instanceType.isDefined) { publication.getInstance().asScala.foreach(i => i.setInstancetype(instanceType.get.getInstancetype)) } - publication.getInstance().asScala.foreach(i => { var hb = new KeyValue if (item != null) { @@ -179,7 +172,6 @@ object DoiBoostMappingUtil { publication } - def generateDSId(input: String): String = { val b = StringUtils.substringBefore(input, "::") @@ -187,12 +179,10 @@ object DoiBoostMappingUtil { s"10|${b}::${DHPUtils.md5(a)}" } - def generateDataInfo(): DataInfo = { generateDataInfo("0.9") } - def filterPublication(publication: Publication): Boolean = { //Case empty publication @@ -264,7 +254,7 @@ object DoiBoostMappingUtil { di.setInferred(false) di.setInvisible(false) di.setTrust(trust) - di.setProvenanceaction(OafUtils.createQualifier("sysimport:actionset", "dnet:provenanceActions")) + di.setProvenanceaction(OafUtils.createQualifier(ModelConstants.SYSIMPORT_ACTIONSET, ModelConstants.DNET_PROVENANCE_ACTIONS)) di } @@ -330,8 +320,8 @@ object DoiBoostMappingUtil { def createORIDCollectedFrom(): KeyValue = { val cf = new KeyValue - cf.setValue(ORCID) - cf.setKey("10|" + OPENAIRE_PREFIX + SEPARATOR + DHPUtils.md5(ORCID.toLowerCase)) + cf.setValue(StringUtils.upperCase(ModelConstants.ORCID)) + cf.setKey("10|" + OPENAIRE_PREFIX + SEPARATOR + DHPUtils.md5(ModelConstants.ORCID.toLowerCase)) cf } diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala index b051177f5..5bbfa8ac0 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/crossref/Crossref2Oaf.scala @@ -1,5 +1,6 @@ package eu.dnetlib.doiboost.crossref +import eu.dnetlib.dhp.schema.common.ModelConstants import eu.dnetlib.dhp.schema.oaf._ import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory import eu.dnetlib.dhp.utils.DHPUtils @@ -14,7 +15,6 @@ import org.slf4j.{Logger, LoggerFactory} import scala.collection.JavaConverters._ import scala.collection.mutable import scala.util.matching.Regex - import eu.dnetlib.dhp.schema.scholexplorer.OafUtils case class CrossrefDT(doi: String, json:String, timestamp: Long) {} @@ -88,7 +88,7 @@ case object Crossref2Oaf { //MAPPING Crossref DOI into PID val doi: String = (json \ "DOI").extract[String] - result.setPid(List(createSP(doi, "doi", PID_TYPES)).asJava) + result.setPid(List(createSP(doi, "doi", ModelConstants.DNET_PID_TYPES)).asJava) //MAPPING Crossref DOI into OriginalId //and Other Original Identifier of dataset like clinical-trial-number @@ -186,13 +186,13 @@ case object Crossref2Oaf { if(has_review != JNothing) { instance.setRefereed( - OafUtils.createQualifier("0001", "peerReviewed", "dnet:review_levels", "dnet:review_levels")) + OafUtils.createQualifier("0001", "peerReviewed", ModelConstants.DNET_REVIEW_LEVELS, ModelConstants.DNET_REVIEW_LEVELS)) } instance.setAccessright(getRestrictedQualifier()) result.setInstance(List(instance).asJava) - instance.setInstancetype(OafUtils.createQualifier(cobjCategory.substring(0, 4), cobjCategory.substring(5), "dnet:publication_resource", "dnet:publication_resource")) + instance.setInstancetype(OafUtils.createQualifier(cobjCategory.substring(0, 4), cobjCategory.substring(5), ModelConstants.DNET_PUBLICATION_RESOURCE, ModelConstants.DNET_PUBLICATION_RESOURCE)) result.setResourcetype(OafUtils.createQualifier(cobjCategory.substring(0, 4),"dnet:dataCite_resource")) instance.setCollectedfrom(createCrossrefCollectedFrom()) @@ -221,7 +221,7 @@ case object Crossref2Oaf { a.setFullname(s"$given $family") a.setRank(index+1) if (StringUtils.isNotBlank(orcid)) - a.setPid(List(createSP(orcid, ORCID_PENDING, PID_TYPES, generateDataInfo())).asJava) + a.setPid(List(createSP(orcid, ModelConstants.ORCID_PENDING, ModelConstants.DNET_PID_TYPES, generateDataInfo())).asJava) a } diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala index 6a8ae9928..11a335700 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/mag/MagDataModel.scala @@ -1,6 +1,7 @@ package eu.dnetlib.doiboost.mag +import eu.dnetlib.dhp.schema.common.ModelConstants import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory import eu.dnetlib.dhp.schema.oaf.{Instance, Journal, Publication, StructuredProperty} import eu.dnetlib.doiboost.DoiBoostMappingUtil @@ -191,7 +192,7 @@ case object ConversionUtil { val authors = inputParams._2 val pub = new Publication - pub.setPid(List(createSP(paper.Doi.toLowerCase, "doi", PID_TYPES)).asJava) + pub.setPid(List(createSP(paper.Doi.toLowerCase, "doi", ModelConstants.DNET_PID_TYPES)).asJava) pub.setOriginalId(List(paper.PaperId.toString, paper.Doi.toLowerCase).asJava) //IMPORTANT @@ -200,8 +201,8 @@ case object ConversionUtil { pub.setId(IdentifierFactory.createDOIBoostIdentifier(pub)) - val mainTitles = createSP(paper.PaperTitle, "main title", "dnet:dataCite_title") - val originalTitles = createSP(paper.OriginalTitle, "alternative title", "dnet:dataCite_title") + val mainTitles = createSP(paper.PaperTitle, "main title", ModelConstants.DNET_DATACITE_TITLE) + val originalTitles = createSP(paper.OriginalTitle, "alternative title", ModelConstants.DNET_DATACITE_TITLE) pub.setTitle(List(mainTitles, originalTitles).asJava) pub.setSource(List(asField(paper.BookTitle)).asJava) @@ -214,7 +215,7 @@ case object ConversionUtil { a.setFullname(f.author.DisplayName.get) if(f.affiliation!= null) a.setAffiliation(List(asField(f.affiliation)).asJava) - a.setPid(List(createSP(s"https://academic.microsoft.com/#/detail/${f.author.AuthorId}", "URL", PID_TYPES)).asJava) + a.setPid(List(createSP(s"https://academic.microsoft.com/#/detail/${f.author.AuthorId}", "URL", ModelConstants.DNET_PID_TYPES)).asJava) a } pub.setAuthor(authorsOAF.asJava) @@ -253,14 +254,14 @@ case object ConversionUtil { val description = inputParams._2 val pub = new Publication - pub.setPid(List(createSP(paper.Doi.toLowerCase, "doi", PID_TYPES)).asJava) + pub.setPid(List(createSP(paper.Doi.toLowerCase, "doi", ModelConstants.DNET_PID_TYPES)).asJava) pub.setOriginalId(List(paper.PaperId.toString, paper.Doi.toLowerCase).asJava) //Set identifier as 50 | doiboost____::md5(DOI) pub.setId(generateIdentifier(pub, paper.Doi.toLowerCase)) - val mainTitles = createSP(paper.PaperTitle, "main title", "dnet:dataCite_title") - val originalTitles = createSP(paper.OriginalTitle, "alternative title", "dnet:dataCite_title") + val mainTitles = createSP(paper.PaperTitle, "main title", ModelConstants.DNET_DATACITE_TITLE) + val originalTitles = createSP(paper.OriginalTitle, "alternative title", ModelConstants.DNET_DATACITE_TITLE) pub.setTitle(List(mainTitles, originalTitles).asJava) pub.setSource(List(asField(paper.BookTitle)).asJava) @@ -281,7 +282,7 @@ case object ConversionUtil { a.setAffiliation(List(asField(f.affiliation)).asJava) - a.setPid(List(createSP(s"https://academic.microsoft.com/#/detail/${f.author.AuthorId}", "URL", PID_TYPES)).asJava) + a.setPid(List(createSP(s"https://academic.microsoft.com/#/detail/${f.author.AuthorId}", "URL", ModelConstants.DNET_PID_TYPES)).asJava) a diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala index 02016b47c..8cf6efb49 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala @@ -1,11 +1,12 @@ package eu.dnetlib.doiboost.orcid import com.fasterxml.jackson.databind.ObjectMapper +import eu.dnetlib.dhp.schema.common.ModelConstants import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory import eu.dnetlib.dhp.schema.oaf.{Author, DataInfo, Publication} import eu.dnetlib.dhp.schema.orcid.OrcidDOI import eu.dnetlib.doiboost.DoiBoostMappingUtil -import eu.dnetlib.doiboost.DoiBoostMappingUtil.{ORCID, PID_TYPES, createSP, generateDataInfo, generateIdentifier} +import eu.dnetlib.doiboost.DoiBoostMappingUtil.{createSP, generateDataInfo} import org.apache.commons.lang.StringUtils import org.slf4j.{Logger, LoggerFactory} @@ -48,7 +49,7 @@ object ORCIDToOAF { def convertTOOAF(input:OrcidDOI) :Publication = { val doi = input.getDoi val pub:Publication = new Publication - pub.setPid(List(createSP(doi.toLowerCase, "doi", PID_TYPES)).asJava) + pub.setPid(List(createSP(doi.toLowerCase, "doi", ModelConstants.DNET_PID_TYPES)).asJava) pub.setDataInfo(generateDataInfo()) pub.setId(IdentifierFactory.createDOIBoostIdentifier(pub)) @@ -74,8 +75,8 @@ object ORCIDToOAF { def generateOricPIDDatainfo():DataInfo = { val di =DoiBoostMappingUtil.generateDataInfo("0.91") - di.getProvenanceaction.setClassid("sysimport:crosswalk:entityregistry") - di.getProvenanceaction.setClassname("Harvested") + di.getProvenanceaction.setClassid(ModelConstants.SYSIMPORT_CROSSWALK_ENTITYREGISTRY) + di.getProvenanceaction.setClassname(ModelConstants.HARVESTED) di } @@ -88,7 +89,7 @@ object ORCIDToOAF { else a.setFullname(s"$given $family") if (StringUtils.isNotBlank(orcid)) - a.setPid(List(createSP(orcid, ORCID, PID_TYPES, generateOricPIDDatainfo())).asJava) + a.setPid(List(createSP(orcid, ModelConstants.ORCID, ModelConstants.DNET_PID_TYPES, generateOricPIDDatainfo())).asJava) a } diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java index 8ba397048..3b8c74062 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java @@ -26,12 +26,11 @@ import eu.dnetlib.doiboost.orcidnodoi.util.Pair; /** * This class converts an orcid publication from json format to oaf */ - public class PublicationToOaf implements Serializable { static Logger logger = LoggerFactory.getLogger(PublicationToOaf.class); - public static final String ORCID = "ORCID"; + public static final String ORCID = StringUtils.upperCase(ModelConstants.ORCID); public final static String orcidPREFIX = "orcid_______"; public static final String OPENAIRE_PREFIX = "openaire____"; public static final String SEPARATOR = "::"; @@ -70,7 +69,7 @@ public class PublicationToOaf implements Serializable { private static Map> datasources = new HashMap>() { { - put(ORCID.toLowerCase(), new Pair<>(ORCID, OPENAIRE_PREFIX + SEPARATOR + "orcid")); + put(ORCID.toLowerCase(), new Pair<>(ORCID, OPENAIRE_PREFIX + SEPARATOR + ModelConstants.ORCID)); } }; @@ -135,8 +134,8 @@ public class PublicationToOaf implements Serializable { dataInfo .setProvenanceaction( mapQualifier( - "sysimport:actionset:orcidworks-no-doi", - "sysimport:actionset:orcidworks-no-doi", + ModelConstants.SYSIMPORT_ORCID_NO_DOI, + ModelConstants.SYSIMPORT_ORCID_NO_DOI, ModelConstants.DNET_PROVENANCE_ACTIONS, ModelConstants.DNET_PROVENANCE_ACTIONS)); publication.setDataInfo(dataInfo); @@ -183,15 +182,11 @@ public class PublicationToOaf implements Serializable { } return null; } - Qualifier q = mapQualifier( - "main title", "main title", ModelConstants.DNET_DATACITE_TITLE, ModelConstants.DNET_DATACITE_TITLE); publication .setTitle( titles .stream() - .map(t -> { - return mapStructuredProperty(t, q, null); - }) + .map(t -> mapStructuredProperty(t, ModelConstants.MAIN_TITLE_QUALIFIER, null)) .filter(s -> s != null) .collect(Collectors.toList())); // Adding identifier diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala index b9895dd09..a7f97aaf8 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/uw/UnpayWallToOAF.scala @@ -79,7 +79,7 @@ object UnpayWallToOAF { if (oaLocation.license.isDefined) i.setLicense(asField(oaLocation.license.get)) - pub.setPid(List(createSP(doi, "doi", PID_TYPES)).asJava) + pub.setPid(List(createSP(doi, "doi", ModelConstants.DNET_PID_TYPES)).asJava) // Ticket #6282 Adding open Access Colour if (colour.isDefined) { @@ -90,7 +90,7 @@ object UnpayWallToOAF { a.setSchemename(ModelConstants.DNET_ACCESS_MODES) a.setOpenAccessRoute(colour.get) i.setAccessright(a) - i.setPid(List(createSP(doi, "doi", PID_TYPES)).asJava) + i.setPid(List(createSP(doi, "doi", ModelConstants.DNET_PID_TYPES)).asJava) } pub.setInstance(List(i).asJava) diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/merge/MergeGraphTableSparkJob.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/merge/MergeGraphTableSparkJob.java index 68623dd55..602213e58 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/merge/MergeGraphTableSparkJob.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/merge/MergeGraphTableSparkJob.java @@ -25,6 +25,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.common.HdfsSupport; import eu.dnetlib.dhp.oa.graph.clean.CleanGraphSparkJob; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.schema.oaf.*; import scala.Tuple2; @@ -46,7 +47,7 @@ public class MergeGraphTableSparkJob { static { Qualifier compatibility = new Qualifier(); - compatibility.setClassid("UNKNOWN"); + compatibility.setClassid(ModelConstants.UNKNOWN); DATASOURCE.setOpenairecompatibility(compatibility); } diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java index 7592ee525..bb6e68b7e 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/OdfToOafMapper.java @@ -13,6 +13,7 @@ import org.dom4j.Node; import eu.dnetlib.dhp.common.PacePerson; import eu.dnetlib.dhp.common.vocabulary.VocabularyGroup; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory; @@ -169,7 +170,7 @@ public class OdfToOafMapper extends AbstractMdRecordToOafMapper { res .add( structuredProperty( - ((Node) o).getText(), "UNKNOWN", "UNKNOWN", DNET_DATACITE_DATE, DNET_DATACITE_DATE, + ((Node) o).getText(), UNKNOWN, UNKNOWN, DNET_DATACITE_DATE, DNET_DATACITE_DATE, info)); } else { res @@ -242,7 +243,7 @@ public class OdfToOafMapper extends AbstractMdRecordToOafMapper { @Override protected Qualifier prepareSoftwareProgrammingLanguage(final Document doc, final DataInfo info) { - return prepareQualifier(doc, "//datacite:format", "dnet:programming_languages"); + return prepareQualifier(doc, "//datacite:format", DNET_PROGRAMMING_LANGUAGES); } @Override diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/sx/ebi/SparkAddLinkUpdates.scala b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/sx/ebi/SparkAddLinkUpdates.scala index d5cdb8a7c..bc04e22e7 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/sx/ebi/SparkAddLinkUpdates.scala +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/sx/ebi/SparkAddLinkUpdates.scala @@ -1,5 +1,6 @@ package eu.dnetlib.dhp.sx.ebi import eu.dnetlib.dhp.application.ArgumentApplicationParser +import eu.dnetlib.dhp.schema.common.ModelConstants import eu.dnetlib.dhp.schema.oaf.{Author, Instance, Journal, KeyValue, Oaf, Publication, Relation, Dataset => OafDataset} import eu.dnetlib.dhp.schema.scholexplorer.OafUtils.createQualifier import eu.dnetlib.dhp.schema.scholexplorer.{DLIDataset, DLIPublication, OafUtils, ProvenaceInfo} @@ -50,7 +51,7 @@ case class EBILinks(relation:String, pubdate:String, tpid:String, tpidType:Strin val p = new DLIPublication p.setId(dnetPublicationId) p.setDataInfo(OafUtils.generateDataInfo()) - p.setPid(List(OafUtils.createSP(input.getPmid.toLowerCase.trim, "pmid", "dnet:pid_types")).asJava) + p.setPid(List(OafUtils.createSP(input.getPmid.toLowerCase.trim, "pmid", ModelConstants.DNET_PID_TYPES)).asJava) p.setCompletionStatus("complete") val pi = new ProvenaceInfo pi.setId("dli_________::europe_pmc__") @@ -76,13 +77,13 @@ case class EBILinks(relation:String, pubdate:String, tpid:String, tpidType:Strin if (input.getJournal != null) p.setJournal(journalToOAF(input.getJournal)) - p.setTitle(List(OafUtils.createSP(input.getTitle, "main title", "dnet:dataCite_title")).asJava) + p.setTitle(List(OafUtils.createSP(input.getTitle, "main title", ModelConstants.DNET_DATACITE_TITLE)).asJava) p.setDateofacceptance(OafUtils.asField(input.getDate)) val i = new Instance i.setCollectedfrom(generatePubmedDLICollectedFrom()) i.setDateofacceptance(p.getDateofacceptance) i.setUrl(List(s"https://pubmed.ncbi.nlm.nih.gov/${input.getPmid}").asJava) - i.setInstancetype(createQualifier("0001", "Article", "dnet:publication_resource", "dnet:publication_resource")) + i.setInstancetype(createQualifier("0001", "Article", ModelConstants.DNET_PUBLICATION_RESOURCE, ModelConstants.DNET_PUBLICATION_RESOURCE)) p.setInstance(List(i).asJava) p } @@ -139,7 +140,7 @@ case class EBILinks(relation:String, pubdate:String, tpid:String, tpidType:Strin val d = new DLIDataset d.setId(targetDnetId) d.setDataInfo(OafUtils.generateDataInfo()) - d.setPid(List(OafUtils.createSP(l.tpid.toLowerCase.trim, l.tpidType.toLowerCase.trim, "dnet:pid_types")).asJava) + d.setPid(List(OafUtils.createSP(l.tpid.toLowerCase.trim, l.tpidType.toLowerCase.trim, ModelConstants.DNET_PID_TYPES)).asJava) d.setCompletionStatus("complete") val pi = new ProvenaceInfo pi.setId("dli_________::europe_pmc__") @@ -149,13 +150,13 @@ case class EBILinks(relation:String, pubdate:String, tpid:String, tpidType:Strin d.setDlicollectedfrom(List(pi).asJava) d.setCollectedfrom(List(generatePubmedDLICollectedFrom()).asJava) d.setPublisher(OafUtils.asField(l.publisher)) - d.setTitle(List(OafUtils.createSP(l.title, "main title", "dnet:dataCite_title")).asJava) + d.setTitle(List(OafUtils.createSP(l.title, "main title", ModelConstants.DNET_DATACITE_TITLE)).asJava) d.setDateofacceptance(OafUtils.asField(l.pubdate)) val i = new Instance i.setCollectedfrom(generatePubmedDLICollectedFrom()) i.setDateofacceptance(d.getDateofacceptance) i.setUrl(List(l.turl).asJava) - i.setInstancetype(createQualifier("0021", "Dataset", "dnet:publication_resource", "dnet:publication_resource")) + i.setInstancetype(createQualifier("0021", "Dataset", ModelConstants.DNET_PUBLICATION_RESOURCE, ModelConstants.DNET_PUBLICATION_RESOURCE)) d.setInstance(List(i).asJava) List(relation, inverseRelation, d) }) diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/sx/graph/parser/AbstractScholexplorerParser.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/sx/graph/parser/AbstractScholexplorerParser.java index f56760c82..6e3dad7fd 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/sx/graph/parser/AbstractScholexplorerParser.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/sx/graph/parser/AbstractScholexplorerParser.java @@ -13,6 +13,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import eu.dnetlib.dhp.parser.utility.VtdUtilityParser; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.dhp.schema.scholexplorer.DLIUnknown; import eu.dnetlib.dhp.schema.scholexplorer.ProvenaceInfo; @@ -76,8 +77,8 @@ public abstract class AbstractScholexplorerParser { final Qualifier pidType = new Qualifier(); pidType.setClassname(result.getAttributes().get(fieldName)); pidType.setClassid(result.getAttributes().get(fieldName)); - pidType.setSchemename("dnet:pid_types"); - pidType.setSchemeid("dnet:pid_types"); + pidType.setSchemename(ModelConstants.DNET_PID_TYPES); + pidType.setSchemeid(ModelConstants.DNET_PID_TYPES); pid.setQualifier(pidType); return pid; } @@ -90,8 +91,8 @@ public abstract class AbstractScholexplorerParser { input.setValue(matcher.group()); if (input.getQualifier() == null) { input.setQualifier(new Qualifier()); - input.getQualifier().setSchemename("dnet:pid_types"); - input.getQualifier().setSchemeid("dnet:pid_types"); + input.getQualifier().setSchemename(ModelConstants.DNET_PID_TYPES); + input.getQualifier().setSchemeid(ModelConstants.DNET_PID_TYPES); } input.getQualifier().setClassid("doi"); input.getQualifier().setClassname("doi"); @@ -141,8 +142,8 @@ public abstract class AbstractScholexplorerParser { final Qualifier pt = new Qualifier(); pt.setClassname(pidType); pt.setClassid(pidType); - pt.setSchemename("dnet:pid_types"); - pt.setSchemeid("dnet:pid_types"); + pt.setSchemename(ModelConstants.DNET_PID_TYPES); + pt.setSchemeid(ModelConstants.DNET_PID_TYPES); sourcePid.setQualifier(pt); uk.setPid(Collections.singletonList(sourcePid)); uk.setDateofcollection(dateOfCollection); diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/sx/graph/parser/DatasetScholexplorerParser.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/sx/graph/parser/DatasetScholexplorerParser.java index 11d9905cc..4493010a0 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/sx/graph/parser/DatasetScholexplorerParser.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/sx/graph/parser/DatasetScholexplorerParser.java @@ -15,6 +15,7 @@ import com.ximpleware.VTDNav; import eu.dnetlib.dhp.parser.utility.VtdUtilityParser; import eu.dnetlib.dhp.parser.utility.VtdUtilityParser.Node; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.dhp.schema.scholexplorer.DLIDataset; import eu.dnetlib.dhp.schema.scholexplorer.ProvenaceInfo; @@ -281,11 +282,7 @@ public class DatasetScholexplorerParser extends AbstractScholexplorerParser { t -> { final StructuredProperty st = new StructuredProperty(); st.setValue(t); - st - .setQualifier( - generateQualifier( - "main title", "main title", "dnet:dataCite_title", - "dnet:dataCite_title")); + st.setQualifier(ModelConstants.MAIN_TITLE_QUALIFIER); return st; }) .collect(Collectors.toList())); diff --git a/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala b/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala index 29d75cdbc..a00a7996f 100644 --- a/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala +++ b/dhp-workflows/dhp-graph-provision-scholexplorer/src/main/java/eu/dnetlib/dhp/export/DLIToOAF.scala @@ -159,7 +159,7 @@ object DLIToOAF { result.setUrl(e.url) result.setRefidentifier(e.pid) result.setDataInfo(generateDataInfo()) - result.setQualifier(createQualifier(e.classId, "dnet:externalReference_typologies")) + result.setQualifier(createQualifier(e.classId, ModelConstants.DNET_EXTERNAL_REF_TYPES)) result }) publication.setExternalReference(eRefs.asJava) @@ -238,7 +238,7 @@ object DLIToOAF { if (inputPublication.getAuthor == null || inputPublication.getAuthor.isEmpty) return null result.setAuthor(inputPublication.getAuthor.asScala.map(convertAuthor).asJava) - result.setResulttype(createQualifier(inputPublication.getResulttype.getClassid, inputPublication.getResulttype.getClassname, "dnet:result_typologies", "dnet:result_typologies")) + result.setResulttype(createQualifier(inputPublication.getResulttype.getClassid, inputPublication.getResulttype.getClassname, ModelConstants.DNET_RESULT_TYPOLOGIES, ModelConstants.DNET_RESULT_TYPOLOGIES)) if (inputPublication.getSubject != null) result.setSubject(inputPublication.getSubject.asScala.map(convertSubject).asJava) @@ -259,7 +259,7 @@ object DLIToOAF { result.setDateofacceptance(asField(inputPublication.getRelevantdate.get(0).getValue)) result.setPublisher(inputPublication.getPublisher) result.setSource(inputPublication.getSource) - result.setBestaccessright(createAccessRight("UNKNOWN", "not available", "dnet:access_modes", "dnet:access_modes")) + result.setBestaccessright(createAccessRight(ModelConstants.UNKNOWN, ModelConstants.NOT_AVAILABLE, ModelConstants.DNET_ACCESS_MODES, ModelConstants.DNET_ACCESS_MODES)) val dois = result.getPid.asScala.filter(p => "doi".equalsIgnoreCase(p.getQualifier.getClassname)).map(p => p.getValue) if (dois.isEmpty) @@ -390,7 +390,7 @@ object DLIToOAF { } def patchTitle(t: StructuredProperty): StructuredProperty = { - t.setQualifier(createQualifier("main title", ModelConstants.DNET_DATACITE_TITLE)) + t.setQualifier(ModelConstants.MAIN_TITLE_QUALIFIER) t } diff --git a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/RelationComparator.java b/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/RelationComparator.java index 84ee013aa..e13bc60eb 100644 --- a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/RelationComparator.java +++ b/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/RelationComparator.java @@ -18,7 +18,7 @@ public class RelationComparator implements Comparator { static { weights.put(ModelConstants.OUTCOME, 0); weights.put(ModelConstants.SUPPLEMENT, 1); - weights.put(ModelConstants.REVIEWS, 2); + weights.put(ModelConstants.REVIEW, 2); weights.put(ModelConstants.CITATION, 3); weights.put(ModelConstants.AFFILIATION, 4); weights.put(ModelConstants.RELATIONSHIP, 5); From 70e49ed53c926ea51b6ae10cbff875a716f60f7d Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 1 Apr 2021 10:30:51 +0200 Subject: [PATCH 194/445] [OpenOrgsWf] trivial refactoring --- .../dhp/oa/dedup/AbstractSparkAction.java | 25 ++++++++ .../eu/dnetlib/dhp/oa/dedup/DedupUtility.java | 34 +++++++---- .../dhp/oa/dedup/SparkCopyOpenorgs.java | 2 +- .../oa/dedup/SparkCopyOpenorgsMergeRels.java | 29 +-------- .../oa/dedup/SparkCopyOpenorgsSimRels.java | 28 +-------- .../dedup/SparkCopyRelationsNoOpenorgs.java | 21 ------- .../dhp/oa/dedup/SparkPrepareNewOrgs.java | 31 +--------- .../dhp/oa/dedup/SparkPrepareOrgRels.java | 60 +++++-------------- .../dhp/oa/dedup/SparkPropagateRelation.java | 10 ---- .../oa/dedup/openorgs/oozie_app/workflow.xml | 4 +- .../dhp/oa/dedup/SparkOpenorgsTest.java | 21 ------- 11 files changed, 71 insertions(+), 194 deletions(-) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java index 708d67f6e..68211fd28 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java @@ -6,10 +6,12 @@ import java.io.Serializable; import java.io.StringReader; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.spark.SparkConf; +import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.SaveMode; import org.apache.spark.sql.SparkSession; @@ -23,6 +25,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.common.HdfsSupport; +import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; @@ -117,4 +120,26 @@ abstract class AbstractSparkAction implements Serializable { .map(p -> p.getValue() + TYPE_VALUE_SEPARATOR + p.getQualifier().getClassid()) .collect(Collectors.joining(SP_SEPARATOR)); } + + protected static MapFunction patchRelFn() { + return value -> { + final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); + if (rel.getDataInfo() == null) { + rel.setDataInfo(new DataInfo()); + } + return rel; + }; + } + + protected boolean isOpenorgs(Relation rel) { + return Optional + .ofNullable(rel.getCollectedfrom()) + .map( + c -> c + .stream() + .filter(kv -> kv.getValue().equals(ModelConstants.OPENORGS_NAME)) + .findFirst() + .isPresent()) + .orElse(false); + } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupUtility.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupUtility.java index 4ee8a08da..5806e9fa4 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupUtility.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/DedupUtility.java @@ -2,14 +2,8 @@ package eu.dnetlib.dhp.oa.dedup; import java.io.StringReader; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.text.Normalizer; import java.util.*; -import java.util.stream.Collectors; -import org.apache.commons.codec.binary.Hex; -import org.apache.commons.lang3.StringUtils; import org.apache.spark.SparkContext; import org.apache.spark.util.LongAccumulator; import org.dom4j.Document; @@ -18,21 +12,19 @@ import org.dom4j.Element; import org.dom4j.io.SAXReader; import com.google.common.collect.Sets; -import com.wcohen.ss.JaroWinkler; -import eu.dnetlib.dhp.schema.oaf.Author; -import eu.dnetlib.dhp.schema.oaf.StructuredProperty; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; import eu.dnetlib.pace.clustering.BlacklistAwareClusteringCombiner; import eu.dnetlib.pace.config.DedupConfig; import eu.dnetlib.pace.model.MapDocument; -import eu.dnetlib.pace.model.Person; -import scala.Tuple2; public class DedupUtility { + public static final String OPENORGS_ID_PREFIX = "openorgs____"; + public static final String CORDA_ID_PREFIX = "corda"; + public static Map constructAccumulator( final DedupConfig dedupConf, final SparkContext context) { @@ -134,4 +126,24 @@ public class DedupUtility { dedupConfig.getWf().setConfigurationId(actionSetId); return dedupConfig; } + + public static int compareOpenOrgIds(String o1, String o2) { + if (o1.contains(OPENORGS_ID_PREFIX) && o2.contains(OPENORGS_ID_PREFIX)) + return o1.compareTo(o2); + if (o1.contains(CORDA_ID_PREFIX) && o2.contains(CORDA_ID_PREFIX)) + return o1.compareTo(o2); + + if (o1.contains(OPENORGS_ID_PREFIX)) + return -1; + if (o2.contains(OPENORGS_ID_PREFIX)) + return 1; + + if (o1.contains(CORDA_ID_PREFIX)) + return -1; + if (o2.contains(CORDA_ID_PREFIX)) + return 1; + + return o1.compareTo(o2); + } + } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java index 7984f0104..543558f36 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java @@ -94,7 +94,7 @@ public class SparkCopyOpenorgs extends AbstractSparkAction { log.info("Number of organization entities processed: {}", entities.count()); - entities = entities.filter(entities.col("id").contains("openorgs____")); + entities = entities.filter(entities.col("id").contains(DedupUtility.OPENORGS_ID_PREFIX)); log.info("Number of Openorgs organization entities: {}", entities.count()); diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java index 00036ebb0..dd74677bc 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java @@ -124,35 +124,10 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { .parquet(outputPath); } - private static MapFunction patchRelFn() { - return value -> { - final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); - if (rel.getDataInfo() == null) { - rel.setDataInfo(new DataInfo()); - } - return rel; - }; - } - private boolean filterOpenorgsRels(Relation rel) { - - if (rel.getRelClass().equals(ModelConstants.IS_SIMILAR_TO) + return rel.getRelClass().equals(ModelConstants.IS_SIMILAR_TO) && rel.getRelType().equals(ModelConstants.ORG_ORG_RELTYPE) - && rel.getSubRelType().equals(ModelConstants.DEDUP)) - return true; - return false; - } - - private boolean isOpenorgs(Relation rel) { - - if (rel.getCollectedfrom() != null) { - for (KeyValue k : rel.getCollectedfrom()) { - if (k.getValue() != null && k.getValue().equals(ModelConstants.OPENORGS_NAME)) { - return true; - } - } - } - return false; + && rel.getSubRelType().equals(ModelConstants.DEDUP); } private Relation rel(String source, String target, String relClass, DedupConfig dedupConf) { diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java index 91229fe53..0aaa1e662 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsSimRels.java @@ -81,34 +81,10 @@ public class SparkCopyOpenorgsSimRels extends AbstractSparkAction { log.info("Copied " + rawRels.count() + " Similarity Relations"); } - private static MapFunction patchRelFn() { - return value -> { - final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); - if (rel.getDataInfo() == null) { - rel.setDataInfo(new DataInfo()); - } - return rel; - }; - } - private boolean filterOpenorgsRels(Relation rel) { - - if (rel.getRelClass().equals(ModelConstants.IS_SIMILAR_TO) + return rel.getRelClass().equals(ModelConstants.IS_SIMILAR_TO) && rel.getRelType().equals(ModelConstants.ORG_ORG_RELTYPE) - && rel.getSubRelType().equals(ModelConstants.DEDUP) && isOpenorgs(rel)) - return true; - return false; + && rel.getSubRelType().equals(ModelConstants.DEDUP) && isOpenorgs(rel); } - private boolean isOpenorgs(Relation rel) { - - if (rel.getCollectedfrom() != null) { - for (KeyValue k : rel.getCollectedfrom()) { - if (k.getValue() != null && k.getValue().equals(ModelConstants.OPENORGS_NAME)) { - return true; - } - } - } - return false; - } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRelationsNoOpenorgs.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRelationsNoOpenorgs.java index 71bab79d0..9ece43891 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRelationsNoOpenorgs.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyRelationsNoOpenorgs.java @@ -86,25 +86,4 @@ public class SparkCopyRelationsNoOpenorgs extends AbstractSparkAction { } - private static MapFunction patchRelFn() { - return value -> { - final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); - if (rel.getDataInfo() == null) { - rel.setDataInfo(new DataInfo()); - } - return rel; - }; - } - - private boolean isOpenorgs(Relation rel) { - - if (rel.getCollectedfrom() != null) { - for (KeyValue k : rel.getCollectedfrom()) { - if (k.getValue() != null && k.getValue().equals("OpenOrgs Database")) { - return true; - } - } - } - return false; - } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java index ac51f3157..94aab20cc 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java @@ -127,7 +127,7 @@ public class SparkPrepareNewOrgs extends AbstractSparkAction { .filter(r -> filterRels(r, "organization")) // take the worst id of the diffrel: .mapToPair(rel -> { - if (compareIds(rel.getSource(), rel.getTarget()) > 0) + if (DedupUtility.compareOpenOrgIds(rel.getSource(), rel.getTarget()) > 0) return new Tuple2<>(rel.getSource(), "diffRel"); else return new Tuple2<>(rel.getTarget(), "diffRel"); @@ -200,35 +200,6 @@ public class SparkPrepareNewOrgs extends AbstractSparkAction { } } - private static MapFunction patchRelFn() { - return value -> { - final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); - if (rel.getDataInfo() == null) { - rel.setDataInfo(new DataInfo()); - } - return rel; - }; - } - - public static int compareIds(String o1, String o2) { - if (o1.contains("openorgs____") && o2.contains("openorgs____")) - return o1.compareTo(o2); - if (o1.contains("corda") && o2.contains("corda")) - return o1.compareTo(o2); - - if (o1.contains("openorgs____")) - return -1; - if (o2.contains("openorgs____")) - return 1; - - if (o1.contains("corda")) - return -1; - if (o2.contains("corda")) - return 1; - - return o1.compareTo(o2); - } - private static boolean filterRels(Relation rel, String entityType) { switch (entityType) { diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java index f1de3609b..53e6724ba 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java @@ -14,7 +14,6 @@ import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoders; import org.apache.spark.sql.SaveMode; import org.apache.spark.sql.SparkSession; -import org.apache.spark.util.LongAccumulator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,9 +23,7 @@ import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.oa.dedup.model.OrgSimRel; import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.common.ModelSupport; -import eu.dnetlib.dhp.schema.oaf.DataInfo; -import eu.dnetlib.dhp.schema.oaf.Organization; -import eu.dnetlib.dhp.schema.oaf.Relation; +import eu.dnetlib.dhp.schema.oaf.*; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; import scala.Tuple2; @@ -35,8 +32,6 @@ import scala.Tuple3; public class SparkPrepareOrgRels extends AbstractSparkAction { private static final Logger log = LoggerFactory.getLogger(SparkPrepareOrgRels.class); - public static final String OPENORGS_ID_PREFIX = "openorgs____"; - public static final String CORDA_ID_PREFIX = "corda"; public SparkPrepareOrgRels(ArgumentApplicationParser parser, SparkSession spark) { super(parser, spark); @@ -141,7 +136,7 @@ public class SparkPrepareOrgRels extends AbstractSparkAction { .filter(r -> filterRels(r, "organization")) // put the best id as source of the diffrel: .map(rel -> { - if (compareIds(rel.getSource(), rel.getTarget()) < 0) + if (DedupUtility.compareOpenOrgIds(rel.getSource(), rel.getTarget()) < 0) return new Tuple2<>(new Tuple2<>(rel.getSource(), rel.getTarget()), "diffRel"); else return new Tuple2<>(new Tuple2<>(rel.getTarget(), rel.getSource()), "diffRel"); @@ -216,17 +211,20 @@ public class SparkPrepareOrgRels extends AbstractSparkAction { .joinWith(entities, relations.col("_2").equalTo(entities.col("_1")), "inner") .map( (MapFunction, Tuple2>, OrgSimRel>) r -> { - + final Organization o = r._2()._2(); return new OrgSimRel( r._1()._1(), - r._2()._2().getOriginalId().get(0), - r._2()._2().getLegalname() != null ? r._2()._2().getLegalname().getValue() : "", - r._2()._2().getLegalshortname() != null ? r._2()._2().getLegalshortname().getValue() : "", - r._2()._2().getCountry() != null ? r._2()._2().getCountry().getClassid() : "", - r._2()._2().getWebsiteurl() != null ? r._2()._2().getWebsiteurl().getValue() : "", - r._2()._2().getCollectedfrom().get(0).getValue(), + o.getOriginalId().get(0), + Optional.ofNullable(o.getLegalname()).map(Field::getValue).orElse(""), + Optional.ofNullable(o.getLegalshortname()).map(Field::getValue).orElse(""), + Optional.ofNullable(o.getCountry()).map(Qualifier::getClassid).orElse(""), + Optional.ofNullable(o.getWebsiteurl()).map(Field::getValue).orElse(""), + Optional + .ofNullable(o.getCollectedfrom()) + .map(c -> Optional.ofNullable(c.get(0)).map(KeyValue::getValue).orElse("")) + .orElse(""), r._1()._3(), - structuredPropertyListToString(r._2()._2().getPid())); + structuredPropertyListToString(o.getPid())); }, Encoders.bean(OrgSimRel.class)) .map( @@ -245,28 +243,9 @@ public class SparkPrepareOrgRels extends AbstractSparkAction { } - public static int compareIds(String o1, String o2) { - if (o1.contains(OPENORGS_ID_PREFIX) && o2.contains(OPENORGS_ID_PREFIX)) - return o1.compareTo(o2); - if (o1.contains(CORDA_ID_PREFIX) && o2.contains(CORDA_ID_PREFIX)) - return o1.compareTo(o2); - - if (o1.contains(OPENORGS_ID_PREFIX)) - return -1; - if (o2.contains(OPENORGS_ID_PREFIX)) - return 1; - - if (o1.contains(CORDA_ID_PREFIX)) - return -1; - if (o2.contains(CORDA_ID_PREFIX)) - return 1; - - return o1.compareTo(o2); - } - // Sort IDs basing on the type. Priority: 1) openorgs, 2)corda, 3)alphabetic public static List sortIds(List ids) { - ids.sort((o1, o2) -> compareIds(o1, o2)); + ids.sort((o1, o2) -> DedupUtility.compareOpenOrgIds(o1, o2)); return ids; } @@ -301,7 +280,7 @@ public class SparkPrepareOrgRels extends AbstractSparkAction { for (String id1 : g._2()) { for (String id2 : g._2()) { if (!id1.equals(id2)) - if (id1.contains(OPENORGS_ID_PREFIX) && !id2.contains("openorgsmesh")) + if (id1.contains(DedupUtility.OPENORGS_ID_PREFIX) && !id2.contains("openorgsmesh")) rels.add(new Tuple2<>(id1, id2)); } } @@ -340,13 +319,4 @@ public class SparkPrepareOrgRels extends AbstractSparkAction { } - private static MapFunction patchRelFn() { - return value -> { - final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); - if (rel.getDataInfo() == null) { - rel.setDataInfo(new DataInfo()); - } - return rel; - }; - } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPropagateRelation.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPropagateRelation.java index f69e9b5f3..8e6e79eb3 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPropagateRelation.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPropagateRelation.java @@ -142,16 +142,6 @@ public class SparkPropagateRelation extends AbstractSparkAction { StringUtils.isNotBlank(r.getRelClass()); } - private static MapFunction patchRelFn() { - return value -> { - final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); - if (rel.getDataInfo() == null) { - rel.setDataInfo(new DataInfo()); - } - return rel; - }; - } - private static String getId(Relation r, FieldType type) { switch (type) { case SOURCE: diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml index d00ebad6c..060d979da 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/openorgs/oozie_app/workflow.xml @@ -111,7 +111,7 @@ --isLookUpUrl${isLookUpUrl} --actionSetId${actionSetIdOpenorgs} --workingPath${workingPath} - --numPartitions8000 + --numPartitions1000 @@ -139,7 +139,7 @@ --isLookUpUrl${isLookUpUrl} --workingPath${workingPath} --actionSetId${actionSetIdOpenorgs} - --numPartitions8000 + --numPartitions1000 diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java index 7aaed3de7..419be1da3 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java @@ -3,7 +3,6 @@ package eu.dnetlib.dhp.oa.dedup; import static java.nio.file.Files.createTempDirectory; -import static org.apache.spark.sql.functions.count; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.lenient; @@ -16,14 +15,7 @@ import java.nio.file.Paths; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; -import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; -import org.apache.spark.api.java.function.FilterFunction; -import org.apache.spark.api.java.function.ForeachFunction; -import org.apache.spark.api.java.function.MapFunction; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Encoders; -import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; @@ -35,12 +27,8 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.application.ArgumentApplicationParser; -import eu.dnetlib.dhp.schema.oaf.DataInfo; -import eu.dnetlib.dhp.schema.oaf.Relation; -import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; -import jdk.nashorn.internal.ir.annotations.Ignore; @ExtendWith(MockitoExtension.class) public class SparkOpenorgsTest implements Serializable { @@ -224,13 +212,4 @@ public class SparkOpenorgsTest implements Serializable { FileUtils.deleteDirectory(new File(testDedupGraphBasePath)); } - private static MapFunction patchRelFn() { - return value -> { - final Relation rel = OBJECT_MAPPER.readValue(value, Relation.class); - if (rel.getDataInfo() == null) { - rel.setDataInfo(new DataInfo()); - } - return rel; - }; - } } From ee34cc51c3914c73bc7344424cda747810e691cb Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 1 Apr 2021 17:07:49 +0200 Subject: [PATCH 195/445] [ORCID-no-doi] integrating PR#98 https://code-repo.d4science.org/D-Net/dnet-hadoop/pulls/98 --- .../dnetlib/dhp/schema/oaf/Relation.java.rej | 31 + .../dhp/schema/orcid/AuthorHistory.java | 79 ++ .../dhp/schema/orcid/AuthorSummary.java | 25 + .../dhp/schema/orcid}/Contributor.java | 8 +- .../dnetlib/dhp/schema/orcid}/ExternalId.java | 6 +- .../dnetlib/dhp/schema/orcid/OrcidData.java | 34 + .../dhp/schema/orcid}/PublicationDate.java | 6 +- .../eu/dnetlib/dhp/schema/orcid/Summary.java | 79 ++ .../eu/dnetlib/dhp/schema/orcid/Work.java | 16 + .../dnetlib/dhp/schema/orcid/WorkDetail.java | 9 +- .../doiboost/orcid/OrcidDownloader.java | 208 --- .../orcid/SparkDownloadOrcidAuthors.java | 113 +- .../orcid/SparkDownloadOrcidAuthors.java.rej | 30 + .../orcid/SparkDownloadOrcidWorks.java | 251 ++++ .../orcid/SparkGenLastModifiedSeq.java | 15 +- .../orcid/SparkGenerateDoiAuthorList.java | 46 +- .../orcid/SparkUpdateOrcidAuthors.java | 242 ++++ .../orcid/SparkUpdateOrcidDatasets.java | 317 +++++ .../doiboost/orcid/SparkUpdateOrcidWorks.java | 186 +++ .../doiboost/orcid/json/JsonHelper.java | 4 +- .../dnetlib/doiboost/orcid/util/HDFSUtil.java | 67 + .../doiboost/orcid/xml/XMLRecordParser.java | 208 ++- .../orcidnodoi/ActivitiesDumpReader.java | 18 +- .../SparkGenEnrichedOrcidWorks.java | 111 +- .../doiboost/orcidnodoi/json/JsonWriter.java | 4 + .../orcidnodoi/oaf/PublicationToOaf.java | 148 +- .../orcidnodoi/oaf/PublicationToOaf.java.rej | 77 ++ .../orcidnodoi/similarity/AuthorMatcher.java | 6 +- .../orcidnodoi/xml/XMLRecordParserNoDoi.java | 12 +- .../gen_doi_author_list_orcid_parameters.json | 2 + ...ters.json => gen_orcid-no-doi_params.json} | 3 +- .../oozie_app/config-default.xml | 18 - .../oozie_app/workflow.xml | 148 +- .../orcid_update/oozie_app/workflow.xml | 163 +++ .../oozie_app/config-default.xml | 22 - .../oozie_app/workflow.xml | 206 ++- .../orcidnodoi/mappings/typologies.json | 26 +- .../orcidnodoi/oozie_app/workflow.xml | 19 +- .../doiboost/orcid/OrcidClientTest.java | 255 ++-- .../orcid/xml/XMLRecordParserTest.java | 80 +- .../orcidnodoi/xml/OrcidNoDoiTest.java | 14 +- ...0000-0002-6664-7451_work.compressed.base64 | 1 + .../0000-0003-3028-6161.compressed.base64 | 2 +- .../orcid/xml/record_0000-0001-5004-5918.xml | 1202 +++++++++++++++++ .../orcid/xml/record_8888-8888-8888-8880.xml | 2 +- 45 files changed, 3844 insertions(+), 675 deletions(-) create mode 100644 dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Relation.java.rej create mode 100644 dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/AuthorHistory.java create mode 100644 dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/AuthorSummary.java rename {dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/model => dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid}/Contributor.java (84%) rename {dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/model => dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid}/ExternalId.java (82%) create mode 100644 dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/OrcidData.java rename {dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/model => dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid}/PublicationDate.java (80%) create mode 100644 dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/Summary.java create mode 100644 dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/Work.java rename dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/model/WorkDataNoDoi.java => dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/WorkDetail.java (86%) delete mode 100644 dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/OrcidDownloader.java create mode 100644 dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidAuthors.java.rej create mode 100644 dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidWorks.java create mode 100644 dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkUpdateOrcidAuthors.java create mode 100644 dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkUpdateOrcidDatasets.java create mode 100644 dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkUpdateOrcidWorks.java create mode 100644 dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/util/HDFSUtil.java create mode 100644 dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java.rej rename dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/{gen_enriched_orcid_works_parameters.json => gen_orcid-no-doi_params.json} (57%) delete mode 100644 dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_doi_author_list/oozie_app/config-default.xml create mode 100644 dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_update/oozie_app/workflow.xml delete mode 100644 dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_updates_download/oozie_app/config-default.xml create mode 100644 dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/0000-0002-6664-7451_work.compressed.base64 create mode 100644 dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/xml/record_0000-0001-5004-5918.xml diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Relation.java.rej b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Relation.java.rej new file mode 100644 index 000000000..7ce658877 --- /dev/null +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Relation.java.rej @@ -0,0 +1,31 @@ +diff a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Relation.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Relation.java (rejected hunks) +@@ -1,8 +1,6 @@ + + package eu.dnetlib.dhp.schema.oaf; + +-import eu.dnetlib.dhp.schema.common.ModelSupport; +- + import static com.google.common.base.Preconditions.checkArgument; + + import java.text.ParseException; +@@ -10,6 +8,8 @@ import java.util.*; + import java.util.stream.Collectors; + import java.util.stream.Stream; + ++import eu.dnetlib.dhp.schema.common.ModelSupport; ++ + /** + * Relation models any edge between two nodes in the OpenAIRE graph. It has a source id and a target id pointing to + * graph node identifiers and it is further characterised by the semantic of the link through the fields relType, +@@ -137,7 +137,10 @@ public class Relation extends Oaf { + try { + setValidationDate(ModelSupport.oldest(getValidationDate(), r.getValidationDate())); + } catch (ParseException e) { +- throw new IllegalArgumentException(String.format("invalid validation date format in relation [s:%s, t:%s]: %s", getSource(), getTarget(), getValidationDate())); ++ throw new IllegalArgumentException(String ++ .format( ++ "invalid validation date format in relation [s:%s, t:%s]: %s", getSource(), getTarget(), ++ getValidationDate())); + } + + super.mergeFrom(r); diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/AuthorHistory.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/AuthorHistory.java new file mode 100644 index 000000000..554aae82c --- /dev/null +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/AuthorHistory.java @@ -0,0 +1,79 @@ + +package eu.dnetlib.dhp.schema.orcid; + +import java.io.Serializable; + +public class AuthorHistory implements Serializable { + private String creationMethod; + private String completionDate; + private String submissionDate; + private String lastModifiedDate; + private boolean claimed; + private String deactivationDate; + private boolean verifiedEmail; + private boolean verifiedPrimaryEmail; + + public String getCreationMethod() { + return creationMethod; + } + + public void setCreationMethod(String creationMethod) { + this.creationMethod = creationMethod; + } + + public String getCompletionDate() { + return completionDate; + } + + public void setCompletionDate(String completionDate) { + this.completionDate = completionDate; + } + + public String getSubmissionDate() { + return submissionDate; + } + + public void setSubmissionDate(String submissionDate) { + this.submissionDate = submissionDate; + } + + public String getLastModifiedDate() { + return lastModifiedDate; + } + + public void setLastModifiedDate(String lastModifiedDate) { + this.lastModifiedDate = lastModifiedDate; + } + + public boolean isClaimed() { + return claimed; + } + + public void setClaimed(boolean claimed) { + this.claimed = claimed; + } + + public String getDeactivationDate() { + return deactivationDate; + } + + public void setDeactivationDate(String deactivationDate) { + this.deactivationDate = deactivationDate; + } + + public boolean isVerifiedEmail() { + return verifiedEmail; + } + + public void setVerifiedEmail(boolean verifiedEmail) { + this.verifiedEmail = verifiedEmail; + } + + public boolean isVerifiedPrimaryEmail() { + return verifiedPrimaryEmail; + } + + public void setVerifiedPrimaryEmail(boolean verifiedPrimaryEmail) { + this.verifiedPrimaryEmail = verifiedPrimaryEmail; + } +} diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/AuthorSummary.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/AuthorSummary.java new file mode 100644 index 000000000..794576628 --- /dev/null +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/AuthorSummary.java @@ -0,0 +1,25 @@ + +package eu.dnetlib.dhp.schema.orcid; + +import java.io.Serializable; + +public class AuthorSummary extends OrcidData implements Serializable { + private AuthorData authorData; + private AuthorHistory authorHistory; + + public AuthorData getAuthorData() { + return authorData; + } + + public void setAuthorData(AuthorData authorData) { + this.authorData = authorData; + } + + public AuthorHistory getAuthorHistory() { + return authorHistory; + } + + public void setAuthorHistory(AuthorHistory authorHistory) { + this.authorHistory = authorHistory; + } +} diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/model/Contributor.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/Contributor.java similarity index 84% rename from dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/model/Contributor.java rename to dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/Contributor.java index 9222c1cc4..3b543db4b 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/model/Contributor.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/Contributor.java @@ -1,5 +1,5 @@ -package eu.dnetlib.doiboost.orcidnodoi.model; +package eu.dnetlib.dhp.schema.orcid; import java.io.Serializable; @@ -12,9 +12,9 @@ import eu.dnetlib.dhp.schema.orcid.AuthorData; public class Contributor extends AuthorData implements Serializable { private String sequence; private String role; - private transient boolean simpleMatch = false; - private transient Double score = 0.0; - private transient boolean bestMatch = false; + private transient boolean simpleMatch; + private transient Double score; + private transient boolean bestMatch; public String getSequence() { return sequence; diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/model/ExternalId.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/ExternalId.java similarity index 82% rename from dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/model/ExternalId.java rename to dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/ExternalId.java index 7fe50ce25..d8f001aa5 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/model/ExternalId.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/ExternalId.java @@ -1,11 +1,13 @@ -package eu.dnetlib.doiboost.orcidnodoi.model; +package eu.dnetlib.dhp.schema.orcid; + +import java.io.Serializable; /** * This class models the data related to external id, that are retrieved from an orcid publication */ -public class ExternalId { +public class ExternalId implements Serializable { private String type; private String value; private String relationShip; diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/OrcidData.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/OrcidData.java new file mode 100644 index 000000000..606eea6a8 --- /dev/null +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/OrcidData.java @@ -0,0 +1,34 @@ + +package eu.dnetlib.dhp.schema.orcid; + +import java.io.Serializable; + +public class OrcidData implements Serializable { + protected String base64CompressData; + protected String statusCode; + protected String downloadDate; + + public String getBase64CompressData() { + return base64CompressData; + } + + public void setBase64CompressData(String base64CompressData) { + this.base64CompressData = base64CompressData; + } + + public String getStatusCode() { + return statusCode; + } + + public void setStatusCode(String statusCode) { + this.statusCode = statusCode; + } + + public String getDownloadDate() { + return downloadDate; + } + + public void setDownloadDate(String downloadDate) { + this.downloadDate = downloadDate; + } +} diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/model/PublicationDate.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/PublicationDate.java similarity index 80% rename from dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/model/PublicationDate.java rename to dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/PublicationDate.java index 5f794d8eb..01972ce95 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/model/PublicationDate.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/PublicationDate.java @@ -1,11 +1,13 @@ -package eu.dnetlib.doiboost.orcidnodoi.model; +package eu.dnetlib.dhp.schema.orcid; + +import java.io.Serializable; /** * This class models the data related to a publication date, that are retrieved from an orcid publication */ -public class PublicationDate { +public class PublicationDate implements Serializable { private String year; private String month; private String day; diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/Summary.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/Summary.java new file mode 100644 index 000000000..ffebf5021 --- /dev/null +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/Summary.java @@ -0,0 +1,79 @@ + +package eu.dnetlib.dhp.schema.orcid; + +import java.io.Serializable; + +public class Summary implements Serializable { + private String creationMethod; + private String completionDate; + private String submissionDate; + private String lastModifiedDate; + private boolean claimed; + private String deactivationDate; + private boolean verifiedEmail; + private boolean verifiedPrimaryEmail; + + public String getCreationMethod() { + return creationMethod; + } + + public void setCreationMethod(String creationMethod) { + this.creationMethod = creationMethod; + } + + public String getCompletionDate() { + return completionDate; + } + + public void setCompletionDate(String completionDate) { + this.completionDate = completionDate; + } + + public String getSubmissionDate() { + return submissionDate; + } + + public void setSubmissionDate(String submissionDate) { + this.submissionDate = submissionDate; + } + + public String getLastModifiedDate() { + return lastModifiedDate; + } + + public void setLastModifiedDate(String lastModifiedDate) { + this.lastModifiedDate = lastModifiedDate; + } + + public boolean isClaimed() { + return claimed; + } + + public void setClaimed(boolean claimed) { + this.claimed = claimed; + } + + public String getDeactivationDate() { + return deactivationDate; + } + + public void setDeactivationDate(String deactivationDate) { + this.deactivationDate = deactivationDate; + } + + public boolean isVerifiedEmail() { + return verifiedEmail; + } + + public void setVerifiedEmail(boolean verifiedEmail) { + this.verifiedEmail = verifiedEmail; + } + + public boolean isVerifiedPrimaryEmail() { + return verifiedPrimaryEmail; + } + + public void setVerifiedPrimaryEmail(boolean verifiedPrimaryEmail) { + this.verifiedPrimaryEmail = verifiedPrimaryEmail; + } +} diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/Work.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/Work.java new file mode 100644 index 000000000..c557eb5d2 --- /dev/null +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/Work.java @@ -0,0 +1,16 @@ + +package eu.dnetlib.dhp.schema.orcid; + +import java.io.Serializable; + +public class Work extends OrcidData implements Serializable { + WorkDetail workDetail; + + public WorkDetail getWorkDetail() { + return workDetail; + } + + public void setWorkDetail(WorkDetail workDetail) { + this.workDetail = workDetail; + } +} diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/model/WorkDataNoDoi.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/WorkDetail.java similarity index 86% rename from dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/model/WorkDataNoDoi.java rename to dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/WorkDetail.java index 58f992d12..614d415c1 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/model/WorkDataNoDoi.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/orcid/WorkDetail.java @@ -1,14 +1,19 @@ -package eu.dnetlib.doiboost.orcidnodoi.model; +package eu.dnetlib.dhp.schema.orcid; import java.io.Serializable; import java.util.List; +import eu.dnetlib.dhp.schema.orcid.Contributor; +import eu.dnetlib.dhp.schema.orcid.ExternalId; +import eu.dnetlib.dhp.schema.orcid.OrcidData; +import eu.dnetlib.dhp.schema.orcid.PublicationDate; + /** * This class models the data that are retrieved from orcid publication */ -public class WorkDataNoDoi implements Serializable { +public class WorkDetail implements Serializable { private String oid; private String id; diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/OrcidDownloader.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/OrcidDownloader.java deleted file mode 100644 index be727ab9f..000000000 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/OrcidDownloader.java +++ /dev/null @@ -1,208 +0,0 @@ - -package eu.dnetlib.doiboost.orcid; - -import java.io.*; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Date; -import java.util.List; - -import org.apache.commons.compress.archivers.tar.TarArchiveEntry; -import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; -import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; -import org.apache.commons.io.IOUtils; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.FSDataInputStream; -import org.apache.hadoop.fs.FileSystem; -import org.apache.hadoop.fs.Path; -import org.apache.hadoop.io.SequenceFile; -import org.apache.hadoop.io.Text; -import org.apache.hadoop.io.compress.GzipCodec; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.mortbay.log.Log; - -import eu.dnetlib.dhp.application.ArgumentApplicationParser; - -public class OrcidDownloader extends OrcidDSManager { - - static final int REQ_LIMIT = 24; - static final int REQ_MAX_TEST = -1; - static final int RECORD_PARSED_COUNTER_LOG_INTERVAL = 500; - static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; - static final String lastUpdate = "2020-09-29 00:00:00"; - private String lambdaFileName; - private String outputPath; - private String token; - - public static void main(String[] args) throws IOException, Exception { - OrcidDownloader orcidDownloader = new OrcidDownloader(); - orcidDownloader.loadArgs(args); - orcidDownloader.parseLambdaFile(); - } - - private String downloadRecord(String orcidId) throws IOException { - try (CloseableHttpClient client = HttpClients.createDefault()) { - HttpGet httpGet = new HttpGet("https://api.orcid.org/v3.0/" + orcidId + "/record"); - httpGet.addHeader("Accept", "application/vnd.orcid+xml"); - httpGet.addHeader("Authorization", String.format("Bearer %s", token)); - CloseableHttpResponse response = client.execute(httpGet); - if (response.getStatusLine().getStatusCode() != 200) { - Log - .info( - "Downloading " + orcidId + " status code: " + response.getStatusLine().getStatusCode()); - return new String(""); - } -// return IOUtils.toString(response.getEntity().getContent()); - return xmlStreamToString(response.getEntity().getContent()); - } - } - - private String xmlStreamToString(InputStream xmlStream) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(xmlStream)); - String line; - StringBuffer buffer = new StringBuffer(); - while ((line = br.readLine()) != null) { - buffer.append(line); - } - return buffer.toString(); - } - - public void parseLambdaFile() throws Exception { - int parsedRecordsCounter = 0; - int downloadedRecordsCounter = 0; - int savedRecordsCounter = 0; - long startDownload = 0; - Configuration conf = initConfigurationObject(); - FileSystem fs = initFileSystemObject(conf); - String lambdaFileUri = hdfsServerUri.concat(workingPath).concat(lambdaFileName); - Path hdfsreadpath = new Path(lambdaFileUri); - FSDataInputStream lambdaFileStream = fs.open(hdfsreadpath); - Path hdfsoutputPath = new Path( - hdfsServerUri - .concat(workingPath) - .concat(outputPath) - .concat("updated_xml_authors.seq")); - try (TarArchiveInputStream tais = new TarArchiveInputStream( - new GzipCompressorInputStream(lambdaFileStream))) { - TarArchiveEntry entry = null; - StringBuilder sb = new StringBuilder(); - try (SequenceFile.Writer writer = SequenceFile - .createWriter( - conf, - SequenceFile.Writer.file(hdfsoutputPath), - SequenceFile.Writer.keyClass(Text.class), - SequenceFile.Writer.valueClass(Text.class), - SequenceFile.Writer.compression(SequenceFile.CompressionType.BLOCK, new GzipCodec()))) { - startDownload = System.currentTimeMillis(); - while ((entry = tais.getNextTarEntry()) != null) { - BufferedReader br = new BufferedReader(new InputStreamReader(tais)); // Read directly from tarInput - String line; - while ((line = br.readLine()) != null) { - String[] values = line.split(","); - List recordInfo = Arrays.asList(values); - int nReqTmp = 0; - long startReqTmp = System.currentTimeMillis(); - // skip headers line - if (parsedRecordsCounter == 0) { - parsedRecordsCounter++; - continue; - } - parsedRecordsCounter++; - String orcidId = recordInfo.get(0); - if (isModified(orcidId, recordInfo.get(3))) { - String record = downloadRecord(orcidId); - downloadedRecordsCounter++; - if (!record.isEmpty()) { -// String compressRecord = ArgumentApplicationParser.compressArgument(record); - final Text key = new Text(recordInfo.get(0)); - final Text value = new Text(record); - writer.append(key, value); - savedRecordsCounter++; - } - } else { - break; - } - long endReq = System.currentTimeMillis(); - nReqTmp++; - if (nReqTmp == REQ_LIMIT) { - long reqSessionDuration = endReq - startReqTmp; - if (reqSessionDuration <= 1000) { - Log - .info( - "\nreqSessionDuration: " - + reqSessionDuration - + " nReqTmp: " - + nReqTmp - + " wait ...."); - Thread.sleep(1000 - reqSessionDuration); - } else { - nReqTmp = 0; - startReqTmp = System.currentTimeMillis(); - } - } - if ((parsedRecordsCounter % RECORD_PARSED_COUNTER_LOG_INTERVAL) == 0) { - Log - .info( - "Current parsed: " - + parsedRecordsCounter - + " downloaded: " - + downloadedRecordsCounter - + " saved: " - + savedRecordsCounter); - if (REQ_MAX_TEST != -1 && parsedRecordsCounter > REQ_MAX_TEST) { - break; - } - } - } - long endDownload = System.currentTimeMillis(); - long downloadTime = endDownload - startDownload; - Log.info("Download time: " + ((downloadTime / 1000) / 60) + " minutes"); - } - } - } - Log.info("Download started at: " + new Date(startDownload).toString()); - Log.info("Download ended at: " + new Date(System.currentTimeMillis()).toString()); - Log.info("Parsed Records Counter: " + parsedRecordsCounter); - Log.info("Downloaded Records Counter: " + downloadedRecordsCounter); - Log.info("Saved Records Counter: " + savedRecordsCounter); - } - - private void loadArgs(String[] args) throws IOException, Exception { - final ArgumentApplicationParser parser = new ArgumentApplicationParser( - IOUtils - .toString( - OrcidDownloader.class - .getResourceAsStream( - "/eu/dnetlib/dhp/doiboost/download_orcid_data.json"))); - parser.parseArgument(args); - - hdfsServerUri = parser.get("hdfsServerUri"); - Log.info("HDFS URI: " + hdfsServerUri); - workingPath = parser.get("workingPath"); - Log.info("Default Path: " + workingPath); - lambdaFileName = parser.get("lambdaFileName"); - Log.info("Lambda File Name: " + lambdaFileName); - outputPath = parser.get("outputPath"); - Log.info("Output Data: " + outputPath); - token = parser.get("token"); - } - - public boolean isModified(String orcidId, String modifiedDate) { - Date modifiedDateDt = null; - Date lastUpdateDt = null; - try { - if (modifiedDate.length() != 19) { - modifiedDate = modifiedDate.substring(0, 19); - } - modifiedDateDt = new SimpleDateFormat(DATE_FORMAT).parse(modifiedDate); - lastUpdateDt = new SimpleDateFormat(DATE_FORMAT).parse(lastUpdate); - } catch (Exception e) { - Log.info("[" + orcidId + "] Parsing date: ", e.getMessage()); - return true; - } - return modifiedDateDt.after(lastUpdateDt); - } -} diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidAuthors.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidAuthors.java index 1422a0840..8cf070213 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidAuthors.java +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidAuthors.java @@ -8,6 +8,7 @@ import java.util.Date; import java.util.Optional; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat; import org.apache.http.client.methods.CloseableHttpResponse; @@ -24,13 +25,13 @@ import org.slf4j.LoggerFactory; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.doiboost.orcid.model.DownloadedRecordData; +import eu.dnetlib.doiboost.orcid.util.HDFSUtil; import scala.Tuple2; public class SparkDownloadOrcidAuthors { static Logger logger = LoggerFactory.getLogger(SparkDownloadOrcidAuthors.class); static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; - static final String lastUpdate = "2020-09-29 00:00:00"; public static void main(String[] args) throws Exception { @@ -53,18 +54,25 @@ public class SparkDownloadOrcidAuthors { final String token = parser.get("token"); final String lambdaFileName = parser.get("lambdaFileName"); logger.info("lambdaFileName: {}", lambdaFileName); + final String hdfsServerUri = parser.get("hdfsServerUri"); SparkConf conf = new SparkConf(); runWithSparkSession( conf, isSparkSessionManaged, spark -> { + String lastUpdate = HDFSUtil.readFromTextFile(hdfsServerUri, workingPath, "last_update.txt"); + logger.info("lastUpdate: {}", lastUpdate); + if (StringUtils.isBlank(lastUpdate)) { + throw new RuntimeException("last update info not found"); + } JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); LongAccumulator parsedRecordsAcc = spark.sparkContext().longAccumulator("parsed_records"); LongAccumulator modifiedRecordsAcc = spark.sparkContext().longAccumulator("to_download_records"); LongAccumulator downloadedRecordsAcc = spark.sparkContext().longAccumulator("downloaded_records"); LongAccumulator errorHTTP403Acc = spark.sparkContext().longAccumulator("error_HTTP_403"); + LongAccumulator errorHTTP404Acc = spark.sparkContext().longAccumulator("error_HTTP_404"); LongAccumulator errorHTTP409Acc = spark.sparkContext().longAccumulator("error_HTTP_409"); LongAccumulator errorHTTP503Acc = spark.sparkContext().longAccumulator("error_HTTP_503"); LongAccumulator errorHTTP525Acc = spark.sparkContext().longAccumulator("error_HTTP_525"); @@ -73,13 +81,14 @@ public class SparkDownloadOrcidAuthors { logger.info("Retrieving data from lamda sequence file"); JavaPairRDD lamdaFileRDD = sc .sequenceFile(workingPath + lambdaFileName, Text.class, Text.class); - logger.info("Data retrieved: " + lamdaFileRDD.count()); + final long lamdaFileRDDCount = lamdaFileRDD.count(); + logger.info("Data retrieved: " + lamdaFileRDDCount); Function, Boolean> isModifiedAfterFilter = data -> { String orcidId = data._1().toString(); String lastModifiedDate = data._2().toString(); parsedRecordsAcc.add(1); - if (isModified(orcidId, lastModifiedDate)) { + if (isModified(orcidId, lastModifiedDate, lastUpdate)) { modifiedRecordsAcc.add(1); return true; } @@ -92,49 +101,42 @@ public class SparkDownloadOrcidAuthors { final DownloadedRecordData downloaded = new DownloadedRecordData(); downloaded.setOrcidId(orcidId); downloaded.setLastModifiedDate(lastModifiedDate); - try (CloseableHttpClient client = HttpClients.createDefault()) { - HttpGet httpGet = new HttpGet("https://api.orcid.org/v3.0/" + orcidId + "/record"); - httpGet.addHeader("Accept", "application/vnd.orcid+xml"); - httpGet.addHeader("Authorization", String.format("Bearer %s", token)); - long startReq = System.currentTimeMillis(); - CloseableHttpResponse response = client.execute(httpGet); - long endReq = System.currentTimeMillis(); - long reqTime = endReq - startReq; - if (reqTime < 1000) { - Thread.sleep(1000 - reqTime); + CloseableHttpClient client = HttpClients.createDefault(); + HttpGet httpGet = new HttpGet("https://api.orcid.org/v3.0/" + orcidId + "/record"); + httpGet.addHeader("Accept", "application/vnd.orcid+xml"); + httpGet.addHeader("Authorization", String.format("Bearer %s", token)); + long startReq = System.currentTimeMillis(); + CloseableHttpResponse response = client.execute(httpGet); + long endReq = System.currentTimeMillis(); + long reqTime = endReq - startReq; + if (reqTime < 1000) { + Thread.sleep(1000 - reqTime); + } + int statusCode = response.getStatusLine().getStatusCode(); + downloaded.setStatusCode(statusCode); + if (statusCode != 200) { + switch (statusCode) { + case 403: + errorHTTP403Acc.add(1); + case 404: + errorHTTP404Acc.add(1); + case 409: + errorHTTP409Acc.add(1); + case 503: + errorHTTP503Acc.add(1); + case 525: + errorHTTP525Acc.add(1); + default: + errorHTTPGenericAcc.add(1); } - int statusCode = response.getStatusLine().getStatusCode(); - downloaded.setStatusCode(statusCode); - if (statusCode != 200) { - switch (statusCode) { - case 403: - errorHTTP403Acc.add(1); - case 409: - errorHTTP409Acc.add(1); - case 503: - errorHTTP503Acc.add(1); - throw new RuntimeException("Orcid request rate limit reached (HTTP 503)"); - case 525: - errorHTTP525Acc.add(1); - default: - errorHTTPGenericAcc.add(1); - logger - .info( - "Downloading " + orcidId + " status code: " - + response.getStatusLine().getStatusCode()); - } - return downloaded.toTuple2(); - } - downloadedRecordsAcc.add(1); - downloaded - .setCompressedData( - ArgumentApplicationParser - .compressArgument(IOUtils.toString(response.getEntity().getContent()))); - } catch (Throwable e) { - logger.info("Downloading " + orcidId, e.getMessage()); - downloaded.setErrorMessage(e.getMessage()); return downloaded.toTuple2(); } + downloadedRecordsAcc.add(1); + downloaded + .setCompressedData( + ArgumentApplicationParser + .compressArgument(IOUtils.toString(response.getEntity().getContent()))); + client.close(); return downloaded.toTuple2(); }; @@ -142,10 +144,12 @@ public class SparkDownloadOrcidAuthors { logger.info("Start execution ..."); JavaPairRDD authorsModifiedRDD = lamdaFileRDD.filter(isModifiedAfterFilter); - logger.info("Authors modified count: " + authorsModifiedRDD.count()); + long authorsModifiedCount = authorsModifiedRDD.count(); + logger.info("Authors modified count: " + authorsModifiedCount); + logger.info("Start downloading ..."); authorsModifiedRDD - .repartition(10) + .repartition(100) .map(downloadRecordFunction) .mapToPair(t -> new Tuple2(new Text(t._1()), new Text(t._2()))) .saveAsNewAPIHadoopFile( @@ -154,10 +158,12 @@ public class SparkDownloadOrcidAuthors { Text.class, SequenceFileOutputFormat.class, sc.hadoopConfiguration()); + logger.info("parsedRecordsAcc: " + parsedRecordsAcc.value().toString()); logger.info("modifiedRecordsAcc: " + modifiedRecordsAcc.value().toString()); logger.info("downloadedRecordsAcc: " + downloadedRecordsAcc.value().toString()); logger.info("errorHTTP403Acc: " + errorHTTP403Acc.value().toString()); + logger.info("errorHTTP404Acc: " + errorHTTP404Acc.value().toString()); logger.info("errorHTTP409Acc: " + errorHTTP409Acc.value().toString()); logger.info("errorHTTP503Acc: " + errorHTTP503Acc.value().toString()); logger.info("errorHTTP525Acc: " + errorHTTP525Acc.value().toString()); @@ -166,18 +172,27 @@ public class SparkDownloadOrcidAuthors { } - private static boolean isModified(String orcidId, String modifiedDate) { + public static boolean isModified(String orcidId, String modifiedDate, String lastUpdate) { Date modifiedDateDt; Date lastUpdateDt; + String lastUpdateRedux = ""; try { + if (modifiedDate.equals("last_modified")) { + return false; + } if (modifiedDate.length() != 19) { modifiedDate = modifiedDate.substring(0, 19); } + if (lastUpdate.length() != 19) { + lastUpdateRedux = lastUpdate.substring(0, 19); + } else { + lastUpdateRedux = lastUpdate; + } modifiedDateDt = new SimpleDateFormat(DATE_FORMAT).parse(modifiedDate); - lastUpdateDt = new SimpleDateFormat(DATE_FORMAT).parse(lastUpdate); + lastUpdateDt = new SimpleDateFormat(DATE_FORMAT).parse(lastUpdateRedux); } catch (Exception e) { - logger.info("[" + orcidId + "] Parsing date: ", e.getMessage()); - return true; + throw new RuntimeException("[" + orcidId + "] modifiedDate <" + modifiedDate + "> lastUpdate <" + lastUpdate + + "> Parsing date: " + e.getMessage()); } return modifiedDateDt.after(lastUpdateDt); } diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidAuthors.java.rej b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidAuthors.java.rej new file mode 100644 index 000000000..fc22d8a7a --- /dev/null +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidAuthors.java.rej @@ -0,0 +1,30 @@ +diff a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidAuthors.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidAuthors.java (rejected hunks) +@@ -31,7 +32,6 @@ public class SparkDownloadOrcidAuthors { + + static Logger logger = LoggerFactory.getLogger(SparkDownloadOrcidAuthors.class); + static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; +- static String lastUpdate; + + public static void main(String[] args) throws Exception { + +@@ -54,14 +54,18 @@ public class SparkDownloadOrcidAuthors { + final String token = parser.get("token"); + final String lambdaFileName = parser.get("lambdaFileName"); + logger.info("lambdaFileName: {}", lambdaFileName); +- +- lastUpdate = HDFSUtil.readFromTextFile(workingPath.concat("last_update.txt")); ++ final String hdfsServerUri = parser.get("hdfsServerUri"); + + SparkConf conf = new SparkConf(); + runWithSparkSession( + conf, + isSparkSessionManaged, + spark -> { ++ String lastUpdate = HDFSUtil.readFromTextFile(hdfsServerUri, workingPath, "last_update.txt"); ++ logger.info("lastUpdate: ", lastUpdate); ++ if (StringUtils.isBlank(lastUpdate)) { ++ throw new RuntimeException("last update info not found"); ++ } + JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); + + LongAccumulator parsedRecordsAcc = spark.sparkContext().longAccumulator("parsed_records"); diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidWorks.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidWorks.java new file mode 100644 index 000000000..57ca2aa71 --- /dev/null +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidWorks.java @@ -0,0 +1,251 @@ + +package eu.dnetlib.doiboost.orcid; + +import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.*; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.io.compress.GzipCodec; +import org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaPairRDD; +import org.apache.spark.api.java.JavaSparkContext; +import org.apache.spark.api.java.function.FlatMapFunction; +import org.apache.spark.api.java.function.Function; +import org.apache.spark.util.LongAccumulator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.doiboost.orcid.model.DownloadedRecordData; +import eu.dnetlib.doiboost.orcid.util.HDFSUtil; +import eu.dnetlib.doiboost.orcid.xml.XMLRecordParser; +import scala.Tuple2; + +public class SparkDownloadOrcidWorks { + + static Logger logger = LoggerFactory.getLogger(SparkDownloadOrcidWorks.class); + public static final String LAMBDA_FILE_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; + public static final DateTimeFormatter LAMBDA_FILE_DATE_FORMATTER = DateTimeFormatter + .ofPattern(LAMBDA_FILE_DATE_FORMAT); + public static final String ORCID_XML_DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + public static final DateTimeFormatter ORCID_XML_DATETIMEFORMATTER = DateTimeFormatter + .ofPattern(ORCID_XML_DATETIME_FORMAT); + + public static void main(String[] args) throws IOException, Exception { + + final ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkDownloadOrcidWorks.class + .getResourceAsStream( + "/eu/dnetlib/dhp/doiboost/download_orcid_data.json"))); + parser.parseArgument(args); + Boolean isSparkSessionManaged = Optional + .ofNullable(parser.get("isSparkSessionManaged")) + .map(Boolean::valueOf) + .orElse(Boolean.TRUE); + logger.info("isSparkSessionManaged: {}", isSparkSessionManaged); + final String workingPath = parser.get("workingPath"); + logger.info("workingPath: ", workingPath); + final String outputPath = parser.get("outputPath"); + final String token = parser.get("token"); + final String hdfsServerUri = parser.get("hdfsServerUri"); + + SparkConf conf = new SparkConf(); + runWithSparkSession( + conf, + isSparkSessionManaged, + spark -> { + final String lastUpdateValue = HDFSUtil.readFromTextFile(hdfsServerUri, workingPath, "last_update.txt"); + logger.info("lastUpdateValue: ", lastUpdateValue); + + JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); + LongAccumulator updatedAuthorsAcc = spark.sparkContext().longAccumulator("updated_authors"); + LongAccumulator parsedAuthorsAcc = spark.sparkContext().longAccumulator("parsed_authors"); + LongAccumulator parsedWorksAcc = spark.sparkContext().longAccumulator("parsed_works"); + LongAccumulator modifiedWorksAcc = spark.sparkContext().longAccumulator("modified_works"); + LongAccumulator maxModifiedWorksLimitAcc = spark + .sparkContext() + .longAccumulator("max_modified_works_limit"); + LongAccumulator errorCodeFoundAcc = spark.sparkContext().longAccumulator("error_code_found"); + LongAccumulator errorLoadingJsonFoundAcc = spark + .sparkContext() + .longAccumulator("error_loading_json_found"); + LongAccumulator errorLoadingXMLFoundAcc = spark + .sparkContext() + .longAccumulator("error_loading_xml_found"); + LongAccumulator errorParsingXMLFoundAcc = spark + .sparkContext() + .longAccumulator("error_parsing_xml_found"); + LongAccumulator downloadedRecordsAcc = spark.sparkContext().longAccumulator("downloaded_records"); + LongAccumulator errorHTTP403Acc = spark.sparkContext().longAccumulator("error_HTTP_403"); + LongAccumulator errorHTTP404Acc = spark.sparkContext().longAccumulator("error_HTTP_404"); + LongAccumulator errorHTTP409Acc = spark.sparkContext().longAccumulator("error_HTTP_409"); + LongAccumulator errorHTTP503Acc = spark.sparkContext().longAccumulator("error_HTTP_503"); + LongAccumulator errorHTTP525Acc = spark.sparkContext().longAccumulator("error_HTTP_525"); + LongAccumulator errorHTTPGenericAcc = spark.sparkContext().longAccumulator("error_HTTP_Generic"); + + JavaPairRDD updatedAuthorsRDD = sc + .sequenceFile(workingPath + "downloads/updated_authors/*", Text.class, Text.class); + updatedAuthorsAcc.setValue(updatedAuthorsRDD.count()); + + FlatMapFunction, String> retrieveWorkUrlFunction = data -> { + String orcidId = data._1().toString(); + String jsonData = data._2().toString(); + List workIds = new ArrayList<>(); + Map workIdLastModifiedDate = new HashMap<>(); + JsonElement jElement = new JsonParser().parse(jsonData); + String statusCode = getJsonValue(jElement, "statusCode"); + if (statusCode.equals("200")) { + String compressedData = getJsonValue(jElement, "compressedData"); + if (StringUtils.isEmpty(compressedData)) { + errorLoadingJsonFoundAcc.add(1); + } else { + String authorSummary = ArgumentApplicationParser.decompressValue(compressedData); + if (StringUtils.isEmpty(authorSummary)) { + errorLoadingXMLFoundAcc.add(1); + } else { + try { + workIdLastModifiedDate = XMLRecordParser + .retrieveWorkIdLastModifiedDate(authorSummary.getBytes()); + } catch (Exception e) { + logger.error("parsing " + orcidId + " [" + jsonData + "]", e); + errorParsingXMLFoundAcc.add(1); + } + } + } + } else { + errorCodeFoundAcc.add(1); + } + parsedAuthorsAcc.add(1); + workIdLastModifiedDate.forEach((k, v) -> { + parsedWorksAcc.add(1); + if (isModified(orcidId, v, lastUpdateValue)) { + modifiedWorksAcc.add(1); + workIds.add(orcidId.concat("/work/").concat(k)); + } + }); + if (workIdLastModifiedDate.size() > 50) { + maxModifiedWorksLimitAcc.add(1); + } + return workIds.iterator(); + }; + + Function> downloadWorkFunction = data -> { + String relativeWorkUrl = data; + String orcidId = relativeWorkUrl.split("/")[0]; + final DownloadedRecordData downloaded = new DownloadedRecordData(); + downloaded.setOrcidId(orcidId); + downloaded.setLastModifiedDate(lastUpdateValue); + CloseableHttpClient client = HttpClients.createDefault(); + HttpGet httpGet = new HttpGet("https://api.orcid.org/v3.0/" + relativeWorkUrl); + httpGet.addHeader("Accept", "application/vnd.orcid+xml"); + httpGet.addHeader("Authorization", String.format("Bearer %s", token)); + long startReq = System.currentTimeMillis(); + CloseableHttpResponse response = client.execute(httpGet); + long endReq = System.currentTimeMillis(); + long reqTime = endReq - startReq; + if (reqTime < 1000) { + Thread.sleep(1000 - reqTime); + } + int statusCode = response.getStatusLine().getStatusCode(); + downloaded.setStatusCode(statusCode); + if (statusCode != 200) { + switch (statusCode) { + case 403: + errorHTTP403Acc.add(1); + case 404: + errorHTTP404Acc.add(1); + case 409: + errorHTTP409Acc.add(1); + case 503: + errorHTTP503Acc.add(1); + case 525: + errorHTTP525Acc.add(1); + default: + errorHTTPGenericAcc.add(1); + logger + .info( + "Downloading " + orcidId + " status code: " + + response.getStatusLine().getStatusCode()); + } + return downloaded.toTuple2(); + } + downloadedRecordsAcc.add(1); + downloaded + .setCompressedData( + ArgumentApplicationParser + .compressArgument(IOUtils.toString(response.getEntity().getContent()))); + client.close(); + return downloaded.toTuple2(); + }; + + updatedAuthorsRDD + .flatMap(retrieveWorkUrlFunction) + .repartition(100) + .map(downloadWorkFunction) + .mapToPair(t -> new Tuple2(new Text(t._1()), new Text(t._2()))) + .saveAsTextFile(workingPath.concat(outputPath), GzipCodec.class); + + logger.info("updatedAuthorsAcc: " + updatedAuthorsAcc.value().toString()); + logger.info("parsedAuthorsAcc: " + parsedAuthorsAcc.value().toString()); + logger.info("parsedWorksAcc: " + parsedWorksAcc.value().toString()); + logger.info("modifiedWorksAcc: " + modifiedWorksAcc.value().toString()); + logger.info("maxModifiedWorksLimitAcc: " + maxModifiedWorksLimitAcc.value().toString()); + logger.info("errorCodeFoundAcc: " + errorCodeFoundAcc.value().toString()); + logger.info("errorLoadingJsonFoundAcc: " + errorLoadingJsonFoundAcc.value().toString()); + logger.info("errorLoadingXMLFoundAcc: " + errorLoadingXMLFoundAcc.value().toString()); + logger.info("errorParsingXMLFoundAcc: " + errorParsingXMLFoundAcc.value().toString()); + logger.info("downloadedRecordsAcc: " + downloadedRecordsAcc.value().toString()); + logger.info("errorHTTP403Acc: " + errorHTTP403Acc.value().toString()); + logger.info("errorHTTP409Acc: " + errorHTTP409Acc.value().toString()); + logger.info("errorHTTP503Acc: " + errorHTTP503Acc.value().toString()); + logger.info("errorHTTP525Acc: " + errorHTTP525Acc.value().toString()); + logger.info("errorHTTPGenericAcc: " + errorHTTPGenericAcc.value().toString()); + }); + + } + + public static boolean isModified(String orcidId, String modifiedDateValue, String lastUpdateValue) { + LocalDate modifiedDate = null; + LocalDate lastUpdate = null; + try { + modifiedDate = LocalDate.parse(modifiedDateValue, SparkDownloadOrcidWorks.ORCID_XML_DATETIMEFORMATTER); + if (lastUpdateValue.length() != 19) { + lastUpdateValue = lastUpdateValue.substring(0, 19); + } + lastUpdate = LocalDate + .parse(lastUpdateValue, SparkDownloadOrcidWorks.LAMBDA_FILE_DATE_FORMATTER); + } catch (Exception e) { + logger.info("[" + orcidId + "] Parsing date: ", e.getMessage()); + throw new RuntimeException("[" + orcidId + "] Parsing date: " + e.getMessage()); + } + return modifiedDate.isAfter(lastUpdate); + } + + private static String getJsonValue(JsonElement jElement, String property) { + if (jElement.getAsJsonObject().has(property)) { + JsonElement name = null; + name = jElement.getAsJsonObject().get(property); + if (name != null && !name.isJsonNull()) { + return name.getAsString(); + } + } + return new String(""); + } +} diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkGenLastModifiedSeq.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkGenLastModifiedSeq.java index f710635ab..d146f712a 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkGenLastModifiedSeq.java +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkGenLastModifiedSeq.java @@ -3,9 +3,7 @@ package eu.dnetlib.doiboost.orcid; import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; +import java.io.*; import java.net.URI; import java.util.Arrays; import java.util.List; @@ -17,6 +15,7 @@ import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; import org.apache.commons.io.IOUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FSDataInputStream; +import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.SequenceFile; @@ -26,6 +25,7 @@ import org.apache.spark.SparkConf; import org.mortbay.log.Log; import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.doiboost.orcid.util.HDFSUtil; public class SparkGenLastModifiedSeq { private static String hdfsServerUri; @@ -50,6 +50,7 @@ public class SparkGenLastModifiedSeq { outputPath = parser.get("outputPath"); lambdaFileName = parser.get("lambdaFileName"); String lambdaFileUri = hdfsServerUri.concat(workingPath).concat(lambdaFileName); + String lastModifiedDateFromLambdaFileUri = "last_modified_date_from_lambda_file.txt"; SparkConf sparkConf = new SparkConf(); runWithSparkSession( @@ -57,6 +58,7 @@ public class SparkGenLastModifiedSeq { isSparkSessionManaged, spark -> { int rowsNum = 0; + String lastModifiedAuthorDate = ""; Path output = new Path( hdfsServerUri .concat(workingPath) @@ -89,10 +91,17 @@ public class SparkGenLastModifiedSeq { final Text value = new Text(recordInfo.get(3)); writer.append(key, value); rowsNum++; + if (rowsNum == 2) { + lastModifiedAuthorDate = value.toString(); + } } + } } } + HDFSUtil + .writeToTextFile( + hdfsServerUri, workingPath, lastModifiedDateFromLambdaFileUri, lastModifiedAuthorDate); Log.info("Saved rows from lamda csv tar file: " + rowsNum); }); } diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkGenerateDoiAuthorList.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkGenerateDoiAuthorList.java index 011c153ec..d831f8509 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkGenerateDoiAuthorList.java +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkGenerateDoiAuthorList.java @@ -4,15 +4,13 @@ package eu.dnetlib.doiboost.orcid; import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.io.IOUtils; import org.apache.hadoop.io.Text; +import org.apache.hadoop.io.compress.GzipCodec; import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaPairRDD; import org.apache.spark.api.java.JavaRDD; @@ -25,13 +23,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.esotericsoftware.minlog.Log; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.JsonElement; import com.google.gson.JsonParser; import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.schema.orcid.AuthorData; +import eu.dnetlib.dhp.schema.orcid.OrcidDOI; import eu.dnetlib.doiboost.orcid.model.WorkData; +import eu.dnetlib.doiboost.orcid.xml.XMLRecordParser; +import eu.dnetlib.doiboost.orcidnodoi.json.JsonWriter; import scala.Tuple2; public class SparkGenerateDoiAuthorList { @@ -56,6 +56,10 @@ public class SparkGenerateDoiAuthorList { logger.info("workingPath: ", workingPath); final String outputDoiAuthorListPath = parser.get("outputDoiAuthorListPath"); logger.info("outputDoiAuthorListPath: ", outputDoiAuthorListPath); + final String authorsPath = parser.get("authorsPath"); + logger.info("authorsPath: ", authorsPath); + final String xmlWorksPath = parser.get("xmlWorksPath"); + logger.info("xmlWorksPath: ", xmlWorksPath); SparkConf conf = new SparkConf(); runWithSparkSession( @@ -65,17 +69,21 @@ public class SparkGenerateDoiAuthorList { JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); JavaPairRDD summariesRDD = sc - .sequenceFile(workingPath + "../orcid_summaries/output/authors.seq", Text.class, Text.class); + .sequenceFile(workingPath.concat(authorsPath), Text.class, Text.class); Dataset summariesDataset = spark .createDataset( summariesRDD.map(seq -> loadAuthorFromJson(seq._1(), seq._2())).rdd(), Encoders.bean(AuthorData.class)); - JavaPairRDD activitiesRDD = sc - .sequenceFile(workingPath + "/output/*.seq", Text.class, Text.class); + JavaPairRDD xmlWorksRDD = sc + .sequenceFile(workingPath.concat(xmlWorksPath), Text.class, Text.class); + Dataset activitiesDataset = spark .createDataset( - activitiesRDD.map(seq -> loadWorkFromJson(seq._1(), seq._2())).rdd(), + xmlWorksRDD + .map(seq -> XMLRecordParser.VTDParseWorkData(seq._2().toString().getBytes())) + .filter(work -> work != null && work.getErrorCode() == null && work.isDoiFound()) + .rdd(), Encoders.bean(WorkData.class)); Function, Tuple2>> toAuthorListFunction = data -> { @@ -135,13 +143,19 @@ public class SparkGenerateDoiAuthorList { } return null; }) - .mapToPair( - s -> { - ObjectMapper mapper = new ObjectMapper(); - return new Tuple2<>(s._1(), mapper.writeValueAsString(s._2())); - }) - .repartition(10) - .saveAsTextFile(workingPath + outputDoiAuthorListPath); + .mapToPair(s -> { + List authorList = s._2(); + Set oidsAlreadySeen = new HashSet<>(); + authorList.removeIf(a -> !oidsAlreadySeen.add(a.getOid())); + return new Tuple2<>(s._1(), authorList); + }) + .map(s -> { + OrcidDOI orcidDOI = new OrcidDOI(); + orcidDOI.setDoi(s._1()); + orcidDOI.setAuthors(s._2()); + return JsonWriter.create(orcidDOI); + }) + .saveAsTextFile(workingPath + outputDoiAuthorListPath, GzipCodec.class); }); } diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkUpdateOrcidAuthors.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkUpdateOrcidAuthors.java new file mode 100644 index 000000000..0eb844fe2 --- /dev/null +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkUpdateOrcidAuthors.java @@ -0,0 +1,242 @@ + +package eu.dnetlib.doiboost.orcid; + +import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; +import static org.apache.spark.sql.functions.*; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.io.compress.GzipCodec; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaRDD; +import org.apache.spark.api.java.JavaSparkContext; +import org.apache.spark.api.java.function.Function; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoders; +import org.apache.spark.util.LongAccumulator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.orcid.AuthorSummary; +import eu.dnetlib.doiboost.orcid.xml.XMLRecordParser; +import scala.Tuple2; + +public class SparkUpdateOrcidAuthors { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() + .setSerializationInclusion(JsonInclude.Include.NON_NULL); + + public static void main(String[] args) throws IOException, Exception { + Logger logger = LoggerFactory.getLogger(SparkUpdateOrcidAuthors.class); + + final ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkUpdateOrcidAuthors.class + .getResourceAsStream( + "/eu/dnetlib/dhp/doiboost/download_orcid_data.json"))); + parser.parseArgument(args); + Boolean isSparkSessionManaged = Optional + .ofNullable(parser.get("isSparkSessionManaged")) + .map(Boolean::valueOf) + .orElse(Boolean.TRUE); + final String workingPath = parser.get("workingPath"); +// final String outputPath = parser.get("outputPath"); + + SparkConf conf = new SparkConf(); + runWithSparkSession( + conf, + isSparkSessionManaged, + spark -> { + JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); + + LongAccumulator oldAuthorsFoundAcc = spark + .sparkContext() + .longAccumulator("old_authors_found"); + LongAccumulator updatedAuthorsFoundAcc = spark + .sparkContext() + .longAccumulator("updated_authors_found"); + LongAccumulator newAuthorsFoundAcc = spark + .sparkContext() + .longAccumulator("new_authors_found"); + LongAccumulator errorCodeAuthorsFoundAcc = spark + .sparkContext() + .longAccumulator("error_code_authors_found"); + LongAccumulator errorLoadingAuthorsJsonFoundAcc = spark + .sparkContext() + .longAccumulator("error_loading_authors_json_found"); + LongAccumulator errorParsingAuthorsXMLFoundAcc = spark + .sparkContext() + .longAccumulator("error_parsing_authors_xml_found"); + + Function, AuthorSummary> retrieveAuthorSummaryFunction = data -> { + AuthorSummary authorSummary = new AuthorSummary(); + String orcidId = data._1().toString(); + String jsonData = data._2().toString(); + JsonElement jElement = new JsonParser().parse(jsonData); + String statusCode = getJsonValue(jElement, "statusCode"); + String downloadDate = getJsonValue(jElement, "lastModifiedDate"); + if (statusCode.equals("200")) { + String compressedData = getJsonValue(jElement, "compressedData"); + if (StringUtils.isEmpty(compressedData)) { + errorLoadingAuthorsJsonFoundAcc.add(1); + } else { + String xmlAuthor = ArgumentApplicationParser.decompressValue(compressedData); + try { + authorSummary = XMLRecordParser + .VTDParseAuthorSummary(xmlAuthor.getBytes()); + authorSummary.setStatusCode(statusCode); + authorSummary.setDownloadDate(Long.toString(System.currentTimeMillis())); + authorSummary.setBase64CompressData(compressedData); + return authorSummary; + } catch (Exception e) { + logger.error("parsing xml " + orcidId + " [" + jsonData + "]", e); + errorParsingAuthorsXMLFoundAcc.add(1); + } + } + } else { + authorSummary.setStatusCode(statusCode); + authorSummary.setDownloadDate(Long.toString(System.currentTimeMillis())); + errorCodeAuthorsFoundAcc.add(1); + } + return authorSummary; + }; + + Dataset downloadedAuthorSummaryDS = spark + .createDataset( + sc + .sequenceFile(workingPath + "downloads/updated_authors/*", Text.class, Text.class) + .map(retrieveAuthorSummaryFunction) + .rdd(), + Encoders.bean(AuthorSummary.class)); + Dataset currentAuthorSummaryDS = spark + .createDataset( + sc + .textFile(workingPath.concat("orcid_dataset/authors/*")) + .map(item -> OBJECT_MAPPER.readValue(item, AuthorSummary.class)) + .rdd(), + Encoders.bean(AuthorSummary.class)); + Dataset mergedAuthorSummaryDS = currentAuthorSummaryDS + .joinWith( + downloadedAuthorSummaryDS, + currentAuthorSummaryDS + .col("authorData.oid") + .equalTo(downloadedAuthorSummaryDS.col("authorData.oid")), + "full_outer") + .map(value -> { + Optional opCurrent = Optional.ofNullable(value._1()); + Optional opDownloaded = Optional.ofNullable(value._2()); + if (!opCurrent.isPresent()) { + newAuthorsFoundAcc.add(1); + return opDownloaded.get(); + } + if (!opDownloaded.isPresent()) { + oldAuthorsFoundAcc.add(1); + return opCurrent.get(); + } + if (opCurrent.isPresent() && opDownloaded.isPresent()) { + updatedAuthorsFoundAcc.add(1); + return opDownloaded.get(); + } + return null; + }, + Encoders.bean(AuthorSummary.class)) + .filter(Objects::nonNull); + + long mergedCount = mergedAuthorSummaryDS.count(); + + Dataset base64DedupedDS = mergedAuthorSummaryDS.dropDuplicates("base64CompressData"); + + List dupOids = base64DedupedDS + .groupBy("authorData.oid") + .agg(count("authorData.oid").alias("oidOccurrenceCount")) + .where("oidOccurrenceCount > 1") + .select("oid") + .toJavaRDD() + .map(row -> row.get(0).toString()) + .collect(); + + JavaRDD dupAuthors = base64DedupedDS + .toJavaRDD() + .filter( + authorSummary -> (Objects.nonNull(authorSummary.getAuthorData()) + && Objects.nonNull(authorSummary.getAuthorData().getOid()))) + .filter(authorSummary -> dupOids.contains(authorSummary.getAuthorData().getOid())); + + Dataset dupAuthorSummaryDS = spark + .createDataset( + dupAuthors.rdd(), + Encoders.bean(AuthorSummary.class)); + List> lastModifiedAuthors = dupAuthorSummaryDS + .groupBy("authorData.oid") + .agg(array_max(collect_list("downloadDate"))) + .map( + row -> new Tuple2<>(row.get(0).toString(), row.get(1).toString()), + Encoders.tuple(Encoders.STRING(), Encoders.STRING())) + .toJavaRDD() + .collect(); + + JavaRDD lastDownloadedAuthors = base64DedupedDS + .toJavaRDD() + .filter( + authorSummary -> (Objects.nonNull(authorSummary.getAuthorData()) + && Objects.nonNull(authorSummary.getAuthorData().getOid()))) + .filter(authorSummary -> { + boolean oidFound = lastModifiedAuthors + .stream() + .filter(a -> a._1().equals(authorSummary.getAuthorData().getOid())) + .count() == 1; + boolean tsFound = lastModifiedAuthors + .stream() + .filter( + a -> a._1().equals(authorSummary.getAuthorData().getOid()) && + a._2().equals(authorSummary.getDownloadDate())) + .count() == 1; + return (oidFound && tsFound) || (!oidFound); + }); + + Dataset cleanedDS = spark + .createDataset( + lastDownloadedAuthors.rdd(), + Encoders.bean(AuthorSummary.class)) + .dropDuplicates("downloadDate", "authorData"); + cleanedDS + .toJavaRDD() + .map(authorSummary -> OBJECT_MAPPER.writeValueAsString(authorSummary)) + .saveAsTextFile(workingPath.concat("orcid_dataset/new_authors"), GzipCodec.class); + long cleanedDSCount = cleanedDS.count(); + + logger.info("report_oldAuthorsFoundAcc: " + oldAuthorsFoundAcc.value().toString()); + logger.info("report_newAuthorsFoundAcc: " + newAuthorsFoundAcc.value().toString()); + logger.info("report_updatedAuthorsFoundAcc: " + updatedAuthorsFoundAcc.value().toString()); + logger.info("report_errorCodeFoundAcc: " + errorCodeAuthorsFoundAcc.value().toString()); + logger.info("report_errorLoadingJsonFoundAcc: " + errorLoadingAuthorsJsonFoundAcc.value().toString()); + logger.info("report_errorParsingXMLFoundAcc: " + errorParsingAuthorsXMLFoundAcc.value().toString()); + logger.info("report_merged_count: " + mergedCount); + logger.info("report_cleaned_count: " + cleanedDSCount); + }); + } + + private static String getJsonValue(JsonElement jElement, String property) { + if (jElement.getAsJsonObject().has(property)) { + JsonElement name = null; + name = jElement.getAsJsonObject().get(property); + if (name != null && !name.isJsonNull()) { + return name.getAsString(); + } + } + return ""; + } +} diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkUpdateOrcidDatasets.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkUpdateOrcidDatasets.java new file mode 100644 index 000000000..71c011ebc --- /dev/null +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkUpdateOrcidDatasets.java @@ -0,0 +1,317 @@ + +package eu.dnetlib.doiboost.orcid; + +import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; + +import java.io.IOException; +import java.util.Objects; +import java.util.Optional; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.io.compress.GzipCodec; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaSparkContext; +import org.apache.spark.api.java.function.Function; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoders; +import org.apache.spark.util.LongAccumulator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.orcid.AuthorSummary; +import eu.dnetlib.dhp.schema.orcid.Work; +import eu.dnetlib.dhp.schema.orcid.WorkDetail; +import eu.dnetlib.doiboost.orcid.xml.XMLRecordParser; +import eu.dnetlib.doiboost.orcidnodoi.xml.XMLRecordParserNoDoi; +import scala.Tuple2; + +public class SparkUpdateOrcidDatasets { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() + .setSerializationInclusion(JsonInclude.Include.NON_NULL); + + public static void main(String[] args) throws IOException, Exception { + Logger logger = LoggerFactory.getLogger(SparkUpdateOrcidDatasets.class); + + final ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkUpdateOrcidDatasets.class + .getResourceAsStream( + "/eu/dnetlib/dhp/doiboost/download_orcid_data.json"))); + parser.parseArgument(args); + Boolean isSparkSessionManaged = Optional + .ofNullable(parser.get("isSparkSessionManaged")) + .map(Boolean::valueOf) + .orElse(Boolean.TRUE); + final String workingPath = parser.get("workingPath"); +// final String outputPath = parser.get("outputPath"); + + SparkConf conf = new SparkConf(); + runWithSparkSession( + conf, + isSparkSessionManaged, + spark -> { + JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); + + LongAccumulator oldAuthorsFoundAcc = spark + .sparkContext() + .longAccumulator("old_authors_found"); + LongAccumulator updatedAuthorsFoundAcc = spark + .sparkContext() + .longAccumulator("updated_authors_found"); + LongAccumulator newAuthorsFoundAcc = spark + .sparkContext() + .longAccumulator("new_authors_found"); + LongAccumulator errorCodeAuthorsFoundAcc = spark + .sparkContext() + .longAccumulator("error_code_authors_found"); + LongAccumulator errorLoadingAuthorsJsonFoundAcc = spark + .sparkContext() + .longAccumulator("error_loading_authors_json_found"); + LongAccumulator errorParsingAuthorsXMLFoundAcc = spark + .sparkContext() + .longAccumulator("error_parsing_authors_xml_found"); + + LongAccumulator oldWorksFoundAcc = spark + .sparkContext() + .longAccumulator("old_works_found"); + LongAccumulator updatedWorksFoundAcc = spark + .sparkContext() + .longAccumulator("updated_works_found"); + LongAccumulator newWorksFoundAcc = spark + .sparkContext() + .longAccumulator("new_works_found"); + LongAccumulator errorCodeWorksFoundAcc = spark + .sparkContext() + .longAccumulator("error_code_works_found"); + LongAccumulator errorLoadingWorksJsonFoundAcc = spark + .sparkContext() + .longAccumulator("error_loading_works_json_found"); + LongAccumulator errorParsingWorksXMLFoundAcc = spark + .sparkContext() + .longAccumulator("error_parsing_works_xml_found"); + +// JavaPairRDD xmlSummariesRDD = sc +// .sequenceFile(workingPath.concat("xml/authors/xml_authors.seq"), Text.class, Text.class); +// xmlSummariesRDD +// .map(seq -> { +// AuthorSummary authorSummary = XMLRecordParser +// .VTDParseAuthorSummary(seq._2().toString().getBytes()); +// authorSummary +// .setBase64CompressData(ArgumentApplicationParser.compressArgument(seq._2().toString())); +// return authorSummary; +// }) +// .filter(authorSummary -> authorSummary != null) +// .map(authorSummary -> JsonWriter.create(authorSummary)) +// .saveAsTextFile(workingPath.concat("orcid_dataset/authors"), GzipCodec.class); +// +// JavaPairRDD xmlWorksRDD = sc +// .sequenceFile(workingPath.concat("xml/works/*"), Text.class, Text.class); +// +// xmlWorksRDD +// .map(seq -> { +// WorkDetail workDetail = XMLRecordParserNoDoi.VTDParseWorkData(seq._2().toString().getBytes()); +// Work work = new Work(); +// work.setWorkDetail(workDetail); +// work.setBase64CompressData(ArgumentApplicationParser.compressArgument(seq._2().toString())); +// return work; +// }) +// .filter(work -> work != null) +// .map(work -> JsonWriter.create(work)) +// .saveAsTextFile(workingPath.concat("orcid_dataset/works"), GzipCodec.class); + +// Function, AuthorSummary> retrieveAuthorSummaryFunction = data -> { +// AuthorSummary authorSummary = new AuthorSummary(); +// String orcidId = data._1().toString(); +// String jsonData = data._2().toString(); +// JsonElement jElement = new JsonParser().parse(jsonData); +// String statusCode = getJsonValue(jElement, "statusCode"); +// String downloadDate = getJsonValue(jElement, "lastModifiedDate"); +// if (statusCode.equals("200")) { +// String compressedData = getJsonValue(jElement, "compressedData"); +// if (StringUtils.isEmpty(compressedData)) { +// errorLoadingAuthorsJsonFoundAcc.add(1); +// } else { +// String xmlAuthor = ArgumentApplicationParser.decompressValue(compressedData); +// try { +// authorSummary = XMLRecordParser +// .VTDParseAuthorSummary(xmlAuthor.getBytes()); +// authorSummary.setStatusCode(statusCode); +// authorSummary.setDownloadDate("2020-11-18 00:00:05.644768"); +// authorSummary.setBase64CompressData(compressedData); +// return authorSummary; +// } catch (Exception e) { +// logger.error("parsing xml " + orcidId + " [" + jsonData + "]", e); +// errorParsingAuthorsXMLFoundAcc.add(1); +// } +// } +// } else { +// authorSummary.setStatusCode(statusCode); +// authorSummary.setDownloadDate("2020-11-18 00:00:05.644768"); +// errorCodeAuthorsFoundAcc.add(1); +// } +// return authorSummary; +// }; +// +// Dataset downloadedAuthorSummaryDS = spark +// .createDataset( +// sc +// .sequenceFile(workingPath + "downloads/updated_authors/*", Text.class, Text.class) +// .map(retrieveAuthorSummaryFunction) +// .rdd(), +// Encoders.bean(AuthorSummary.class)); +// Dataset currentAuthorSummaryDS = spark +// .createDataset( +// sc +// .textFile(workingPath.concat("orcid_dataset/authors/*")) +// .map(item -> OBJECT_MAPPER.readValue(item, AuthorSummary.class)) +// .rdd(), +// Encoders.bean(AuthorSummary.class)); +// currentAuthorSummaryDS +// .joinWith( +// downloadedAuthorSummaryDS, +// currentAuthorSummaryDS +// .col("authorData.oid") +// .equalTo(downloadedAuthorSummaryDS.col("authorData.oid")), +// "full_outer") +// .map(value -> { +// Optional opCurrent = Optional.ofNullable(value._1()); +// Optional opDownloaded = Optional.ofNullable(value._2()); +// if (!opCurrent.isPresent()) { +// newAuthorsFoundAcc.add(1); +// return opDownloaded.get(); +// } +// if (!opDownloaded.isPresent()) { +// oldAuthorsFoundAcc.add(1); +// return opCurrent.get(); +// } +// if (opCurrent.isPresent() && opDownloaded.isPresent()) { +// updatedAuthorsFoundAcc.add(1); +// return opDownloaded.get(); +// } +// return null; +// }, +// Encoders.bean(AuthorSummary.class)) +// .filter(Objects::nonNull) +// .toJavaRDD() +// .map(authorSummary -> OBJECT_MAPPER.writeValueAsString(authorSummary)) +// .saveAsTextFile(workingPath.concat("orcid_dataset/new_authors"), GzipCodec.class); +// +// logger.info("oldAuthorsFoundAcc: " + oldAuthorsFoundAcc.value().toString()); +// logger.info("newAuthorsFoundAcc: " + newAuthorsFoundAcc.value().toString()); +// logger.info("updatedAuthorsFoundAcc: " + updatedAuthorsFoundAcc.value().toString()); +// logger.info("errorCodeFoundAcc: " + errorCodeAuthorsFoundAcc.value().toString()); +// logger.info("errorLoadingJsonFoundAcc: " + errorLoadingAuthorsJsonFoundAcc.value().toString()); +// logger.info("errorParsingXMLFoundAcc: " + errorParsingAuthorsXMLFoundAcc.value().toString()); + + Function retrieveWorkFunction = jsonData -> { + Work work = new Work(); + JsonElement jElement = new JsonParser().parse(jsonData); + String statusCode = getJsonValue(jElement, "statusCode"); + work.setStatusCode(statusCode); + String downloadDate = getJsonValue(jElement, "lastModifiedDate"); + work.setDownloadDate("2020-11-18 00:00:05.644768"); + if (statusCode.equals("200")) { + String compressedData = getJsonValue(jElement, "compressedData"); + if (StringUtils.isEmpty(compressedData)) { + errorLoadingWorksJsonFoundAcc.add(1); + } else { + String xmlWork = ArgumentApplicationParser.decompressValue(compressedData); + try { + WorkDetail workDetail = XMLRecordParserNoDoi + .VTDParseWorkData(xmlWork.getBytes()); + work.setWorkDetail(workDetail); + work.setBase64CompressData(compressedData); + return work; + } catch (Exception e) { + logger.error("parsing xml [" + jsonData + "]", e); + errorParsingWorksXMLFoundAcc.add(1); + } + } + } else { + errorCodeWorksFoundAcc.add(1); + } + return work; + }; + + Dataset downloadedWorksDS = spark + .createDataset( + sc + .textFile(workingPath + "downloads/updated_works/*") + .map(s -> { + return s.substring(21, s.length() - 1); + }) + .map(retrieveWorkFunction) + .rdd(), + Encoders.bean(Work.class)); + Dataset currentWorksDS = spark + .createDataset( + sc + .textFile(workingPath.concat("orcid_dataset/works/*")) + .map(item -> OBJECT_MAPPER.readValue(item, Work.class)) + .rdd(), + Encoders.bean(Work.class)); + currentWorksDS + .joinWith( + downloadedWorksDS, + currentWorksDS + .col("workDetail.id") + .equalTo(downloadedWorksDS.col("workDetail.id")) + .and( + currentWorksDS + .col("workDetail.oid") + .equalTo(downloadedWorksDS.col("workDetail.oid"))), + "full_outer") + .map(value -> { + Optional opCurrent = Optional.ofNullable(value._1()); + Optional opDownloaded = Optional.ofNullable(value._2()); + if (!opCurrent.isPresent()) { + newWorksFoundAcc.add(1); + return opDownloaded.get(); + } + if (!opDownloaded.isPresent()) { + oldWorksFoundAcc.add(1); + return opCurrent.get(); + } + if (opCurrent.isPresent() && opDownloaded.isPresent()) { + updatedWorksFoundAcc.add(1); + return opDownloaded.get(); + } + return null; + }, + Encoders.bean(Work.class)) + .filter(Objects::nonNull) + .toJavaRDD() + .map(work -> OBJECT_MAPPER.writeValueAsString(work)) + .saveAsTextFile(workingPath.concat("orcid_dataset/new_works"), GzipCodec.class); + + logger.info("oldWorksFoundAcc: " + oldWorksFoundAcc.value().toString()); + logger.info("newWorksFoundAcc: " + newWorksFoundAcc.value().toString()); + logger.info("updatedWorksFoundAcc: " + updatedWorksFoundAcc.value().toString()); + logger.info("errorCodeWorksFoundAcc: " + errorCodeWorksFoundAcc.value().toString()); + logger.info("errorLoadingJsonWorksFoundAcc: " + errorLoadingWorksJsonFoundAcc.value().toString()); + logger.info("errorParsingXMLWorksFoundAcc: " + errorParsingWorksXMLFoundAcc.value().toString()); + + }); + } + + private static String getJsonValue(JsonElement jElement, String property) { + if (jElement.getAsJsonObject().has(property)) { + JsonElement name = null; + name = jElement.getAsJsonObject().get(property); + if (name != null && !name.isJsonNull()) { + return name.getAsString(); + } + } + return ""; + } +} diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkUpdateOrcidWorks.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkUpdateOrcidWorks.java new file mode 100644 index 000000000..185e5ec46 --- /dev/null +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkUpdateOrcidWorks.java @@ -0,0 +1,186 @@ + +package eu.dnetlib.doiboost.orcid; + +import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; + +import java.io.IOException; +import java.util.Objects; +import java.util.Optional; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.io.compress.GzipCodec; +import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaSparkContext; +import org.apache.spark.api.java.function.Function; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoders; +import org.apache.spark.util.LongAccumulator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.orcid.Work; +import eu.dnetlib.dhp.schema.orcid.WorkDetail; +import eu.dnetlib.doiboost.orcid.util.HDFSUtil; +import eu.dnetlib.doiboost.orcidnodoi.xml.XMLRecordParserNoDoi; + +public class SparkUpdateOrcidWorks { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() + .setSerializationInclusion(JsonInclude.Include.NON_NULL); + + public static void main(String[] args) throws IOException, Exception { + Logger logger = LoggerFactory.getLogger(SparkUpdateOrcidWorks.class); + + final ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkUpdateOrcidWorks.class + .getResourceAsStream( + "/eu/dnetlib/dhp/doiboost/download_orcid_data.json"))); + parser.parseArgument(args); + Boolean isSparkSessionManaged = Optional + .ofNullable(parser.get("isSparkSessionManaged")) + .map(Boolean::valueOf) + .orElse(Boolean.TRUE); + final String workingPath = parser.get("workingPath"); + final String hdfsServerUri = parser.get("hdfsServerUri"); + + SparkConf conf = new SparkConf(); + runWithSparkSession( + conf, + isSparkSessionManaged, + spark -> { + JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); + + LongAccumulator oldWorksFoundAcc = spark + .sparkContext() + .longAccumulator("old_works_found"); + LongAccumulator updatedWorksFoundAcc = spark + .sparkContext() + .longAccumulator("updated_works_found"); + LongAccumulator newWorksFoundAcc = spark + .sparkContext() + .longAccumulator("new_works_found"); + LongAccumulator errorCodeWorksFoundAcc = spark + .sparkContext() + .longAccumulator("error_code_works_found"); + LongAccumulator errorLoadingWorksJsonFoundAcc = spark + .sparkContext() + .longAccumulator("error_loading_works_json_found"); + LongAccumulator errorParsingWorksXMLFoundAcc = spark + .sparkContext() + .longAccumulator("error_parsing_works_xml_found"); + + Function retrieveWorkFunction = jsonData -> { + Work work = new Work(); + JsonElement jElement = new JsonParser().parse(jsonData); + String statusCode = getJsonValue(jElement, "statusCode"); + work.setStatusCode(statusCode); + String downloadDate = getJsonValue(jElement, "lastModifiedDate"); + work.setDownloadDate(Long.toString(System.currentTimeMillis())); + if (statusCode.equals("200")) { + String compressedData = getJsonValue(jElement, "compressedData"); + if (StringUtils.isEmpty(compressedData)) { + errorLoadingWorksJsonFoundAcc.add(1); + } else { + String xmlWork = ArgumentApplicationParser.decompressValue(compressedData); + try { + WorkDetail workDetail = XMLRecordParserNoDoi + .VTDParseWorkData(xmlWork.getBytes()); + work.setWorkDetail(workDetail); + work.setBase64CompressData(compressedData); + return work; + } catch (Exception e) { + logger.error("parsing xml [" + jsonData + "]", e); + errorParsingWorksXMLFoundAcc.add(1); + } + } + } else { + errorCodeWorksFoundAcc.add(1); + } + return work; + }; + + Dataset downloadedWorksDS = spark + .createDataset( + sc + .textFile(workingPath + "downloads/updated_works/*") + .map(s -> { + return s.substring(21, s.length() - 1); + }) + .map(retrieveWorkFunction) + .rdd(), + Encoders.bean(Work.class)); + Dataset currentWorksDS = spark + .createDataset( + sc + .textFile(workingPath.concat("orcid_dataset/works/*")) + .map(item -> OBJECT_MAPPER.readValue(item, Work.class)) + .rdd(), + Encoders.bean(Work.class)); + currentWorksDS + .joinWith( + downloadedWorksDS, + currentWorksDS + .col("workDetail.id") + .equalTo(downloadedWorksDS.col("workDetail.id")) + .and( + currentWorksDS + .col("workDetail.oid") + .equalTo(downloadedWorksDS.col("workDetail.oid"))), + "full_outer") + .map(value -> { + Optional opCurrent = Optional.ofNullable(value._1()); + Optional opDownloaded = Optional.ofNullable(value._2()); + if (!opCurrent.isPresent()) { + newWorksFoundAcc.add(1); + return opDownloaded.get(); + } + if (!opDownloaded.isPresent()) { + oldWorksFoundAcc.add(1); + return opCurrent.get(); + } + if (opCurrent.isPresent() && opDownloaded.isPresent()) { + updatedWorksFoundAcc.add(1); + return opDownloaded.get(); + } + return null; + }, + Encoders.bean(Work.class)) + .filter(Objects::nonNull) + .toJavaRDD() + .map(work -> OBJECT_MAPPER.writeValueAsString(work)) + .saveAsTextFile(workingPath.concat("orcid_dataset/new_works"), GzipCodec.class); + + logger.info("oldWorksFoundAcc: " + oldWorksFoundAcc.value().toString()); + logger.info("newWorksFoundAcc: " + newWorksFoundAcc.value().toString()); + logger.info("updatedWorksFoundAcc: " + updatedWorksFoundAcc.value().toString()); + logger.info("errorCodeWorksFoundAcc: " + errorCodeWorksFoundAcc.value().toString()); + logger.info("errorLoadingJsonWorksFoundAcc: " + errorLoadingWorksJsonFoundAcc.value().toString()); + logger.info("errorParsingXMLWorksFoundAcc: " + errorParsingWorksXMLFoundAcc.value().toString()); + + String lastModifiedDateFromLambdaFile = HDFSUtil + .readFromTextFile(hdfsServerUri, workingPath, "last_modified_date_from_lambda_file.txt"); + HDFSUtil.writeToTextFile(hdfsServerUri, workingPath, "last_update.txt", lastModifiedDateFromLambdaFile); + logger.info("last_update file updated"); + }); + } + + private static String getJsonValue(JsonElement jElement, String property) { + if (jElement.getAsJsonObject().has(property)) { + JsonElement name = null; + name = jElement.getAsJsonObject().get(property); + if (name != null && !name.isJsonNull()) { + return name.getAsString(); + } + } + return ""; + } +} diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/json/JsonHelper.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/json/JsonHelper.java index 94f7d8c91..a2342f7b4 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/json/JsonHelper.java +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/json/JsonHelper.java @@ -3,11 +3,11 @@ package eu.dnetlib.doiboost.orcid.json; import com.google.gson.Gson; -import eu.dnetlib.doiboost.orcidnodoi.model.WorkDataNoDoi; +import eu.dnetlib.dhp.schema.orcid.WorkDetail; public class JsonHelper { - public static String createOidWork(WorkDataNoDoi workData) { + public static String createOidWork(WorkDetail workData) { return new Gson().toJson(workData); } } diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/util/HDFSUtil.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/util/HDFSUtil.java new file mode 100644 index 000000000..977b55a6f --- /dev/null +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/util/HDFSUtil.java @@ -0,0 +1,67 @@ + +package eu.dnetlib.doiboost.orcid.util; + +import java.io.*; +import java.net.URI; +import java.nio.charset.StandardCharsets; + +import org.apache.commons.io.IOUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FSDataInputStream; +import org.apache.hadoop.fs.FSDataOutputStream; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; + +import eu.dnetlib.doiboost.orcid.SparkDownloadOrcidAuthors; + +public class HDFSUtil { + + static Logger logger = LoggerFactory.getLogger(HDFSUtil.class); + + private static FileSystem getFileSystem(String hdfsServerUri) throws IOException { + Configuration conf = new Configuration(); + conf.set("fs.defaultFS", hdfsServerUri); + FileSystem fileSystem = FileSystem.get(conf); + return fileSystem; + } + + public static String readFromTextFile(String hdfsServerUri, String workingPath, String path) throws IOException { + FileSystem fileSystem = getFileSystem(hdfsServerUri); + Path toReadPath = new Path(workingPath.concat(path)); + if (!fileSystem.exists(toReadPath)) { + throw new RuntimeException("File not exist: " + path); + } + logger.info("Last_update_path " + toReadPath.toString()); + FSDataInputStream inputStream = new FSDataInputStream(fileSystem.open(toReadPath)); + BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); + StringBuffer sb = new StringBuffer(); + try { + String line; + while ((line = br.readLine()) != null) { + sb.append(line); + } + } finally { + br.close(); + } + String buffer = sb.toString(); + logger.info("Last_update: " + buffer); + return buffer; + } + + public static void writeToTextFile(String hdfsServerUri, String workingPath, String path, String text) + throws IOException { + FileSystem fileSystem = getFileSystem(hdfsServerUri); + Path toWritePath = new Path(workingPath.concat(path)); + if (fileSystem.exists(toWritePath)) { + fileSystem.delete(toWritePath, true); + } + FSDataOutputStream os = fileSystem.create(toWritePath); + BufferedWriter br = new BufferedWriter(new OutputStreamWriter(os, "UTF-8")); + br.write(text); + br.close(); + } +} diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/xml/XMLRecordParser.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/xml/XMLRecordParser.java index cc9abb621..c98d63b91 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/xml/XMLRecordParser.java +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/xml/XMLRecordParser.java @@ -1,22 +1,19 @@ package eu.dnetlib.doiboost.orcid.xml; -import java.util.Arrays; -import java.util.List; +import java.io.IOException; +import java.util.*; +import org.apache.commons.lang3.StringUtils; import org.mortbay.log.Log; -import com.ximpleware.AutoPilot; -import com.ximpleware.EOFException; -import com.ximpleware.EncodingException; -import com.ximpleware.EntityException; -import com.ximpleware.ParseException; -import com.ximpleware.VTDGen; -import com.ximpleware.VTDNav; +import com.ximpleware.*; import eu.dnetlib.dhp.parser.utility.VtdException; import eu.dnetlib.dhp.parser.utility.VtdUtilityParser; import eu.dnetlib.dhp.schema.orcid.AuthorData; +import eu.dnetlib.dhp.schema.orcid.AuthorHistory; +import eu.dnetlib.dhp.schema.orcid.AuthorSummary; import eu.dnetlib.doiboost.orcid.model.WorkData; public class XMLRecordParser { @@ -32,9 +29,12 @@ public class XMLRecordParser { private static final String NS_RECORD_URL = "http://www.orcid.org/ns/record"; private static final String NS_RECORD = "record"; private static final String NS_ERROR_URL = "http://www.orcid.org/ns/error"; - + private static final String NS_ACTIVITIES = "activities"; + private static final String NS_ACTIVITIES_URL = "http://www.orcid.org/ns/activities"; private static final String NS_WORK = "work"; private static final String NS_WORK_URL = "http://www.orcid.org/ns/work"; + private static final String NS_HISTORY = "history"; + private static final String NS_HISTORY_URL = "http://www.orcid.org/ns/history"; private static final String NS_ERROR = "error"; @@ -51,6 +51,7 @@ public class XMLRecordParser { ap.declareXPathNameSpace(NS_OTHER, NS_OTHER_URL); ap.declareXPathNameSpace(NS_RECORD, NS_RECORD_URL); ap.declareXPathNameSpace(NS_ERROR, NS_ERROR_URL); + ap.declareXPathNameSpace(NS_HISTORY, NS_HISTORY_URL); AuthorData authorData = new AuthorData(); final List errors = VtdUtilityParser.getTextValue(ap, vn, "//error:response-code"); @@ -89,6 +90,46 @@ public class XMLRecordParser { authorData.setOtherNames(otherNames); } +// final String creationMethod = VtdUtilityParser.getSingleValue(ap, vn, "//history:creation-method"); +// if (StringUtils.isNoneBlank(creationMethod)) { +// authorData.setCreationMethod(creationMethod); +// } +// +// final String completionDate = VtdUtilityParser.getSingleValue(ap, vn, "//history:completion-date"); +// if (StringUtils.isNoneBlank(completionDate)) { +// authorData.setCompletionDate(completionDate); +// } +// +// final String submissionDate = VtdUtilityParser.getSingleValue(ap, vn, "//history:submission-date"); +// if (StringUtils.isNoneBlank(submissionDate)) { +// authorData.setSubmissionDate(submissionDate); +// } +// +// final String claimed = VtdUtilityParser.getSingleValue(ap, vn, "//history:claimed"); +// if (StringUtils.isNoneBlank(claimed)) { +// authorData.setClaimed(Boolean.parseBoolean(claimed)); +// } +// +// final String verifiedEmail = VtdUtilityParser.getSingleValue(ap, vn, "//history:verified-email"); +// if (StringUtils.isNoneBlank(verifiedEmail)) { +// authorData.setVerifiedEmail(Boolean.parseBoolean(verifiedEmail)); +// } +// +// final String verifiedPrimaryEmail = VtdUtilityParser.getSingleValue(ap, vn, "//history:verified-primary-email"); +// if (StringUtils.isNoneBlank(verifiedPrimaryEmail)) { +// authorData.setVerifiedPrimaryEmail(Boolean.parseBoolean(verifiedPrimaryEmail)); +// } +// +// final String deactivationDate = VtdUtilityParser.getSingleValue(ap, vn, "//history:deactivation-date"); +// if (StringUtils.isNoneBlank(deactivationDate)) { +// authorData.setDeactivationDate(deactivationDate); +// } +// +// final String lastModifiedDate = VtdUtilityParser +// .getSingleValue(ap, vn, "//history:history/common:last-modified-date"); +// if (StringUtils.isNoneBlank(lastModifiedDate)) { +// authorData.setLastModifiedDate(lastModifiedDate); +// } return authorData; } @@ -139,6 +180,12 @@ public class XMLRecordParser { return retrieveOrcidId(bytes, defaultValue, NS_WORK, NS_WORK_URL, "//work:work", "put-code"); } + public static String retrieveWorkIdFromSummary(byte[] bytes, String defaultValue) + throws VtdException, ParseException { + return retrieveOrcidId( + bytes, defaultValue, NS_ACTIVITIES, NS_ACTIVITIES_URL, "//work:work-summary", "put-code"); + } + private static String retrieveOrcidId(byte[] bytes, String defaultValue, String ns, String nsUrl, String xpath, String idAttributeName) throws VtdException, ParseException { @@ -148,6 +195,7 @@ public class XMLRecordParser { final VTDNav vn = vg.getNav(); final AutoPilot ap = new AutoPilot(vn); ap.declareXPathNameSpace(ns, nsUrl); + ap.declareXPathNameSpace(NS_WORK, NS_WORK_URL); List recordNodes = VtdUtilityParser .getTextValuesWithAttributes( ap, vn, xpath, Arrays.asList(idAttributeName)); @@ -157,4 +205,144 @@ public class XMLRecordParser { Log.info("id not found - default: " + defaultValue); return defaultValue; } + + public static Map retrieveWorkIdLastModifiedDate(byte[] bytes) + throws ParseException, XPathParseException, NavException, XPathEvalException, IOException { + final VTDGen vg = new VTDGen(); + vg.setDoc(bytes); + vg.parse(true); + final VTDNav vn = vg.getNav(); + final AutoPilot ap = new AutoPilot(vn); + ap.declareXPathNameSpace(NS_WORK, NS_WORK_URL); + ap.declareXPathNameSpace(NS_COMMON, NS_COMMON_URL); + Map workIdLastModifiedDate = new HashMap<>(); + ap.selectXPath("//work:work-summary"); + String workId = ""; + while (ap.evalXPath() != -1) { + String lastModifiedDate = ""; + int attr = vn.getAttrVal("put-code"); + if (attr > -1) { + workId = vn.toNormalizedString(attr); + } + if (vn.toElement(VTDNav.FIRST_CHILD, "common:last-modified-date")) { + int val = vn.getText(); + if (val != -1) { + lastModifiedDate = vn.toNormalizedString(val); + workIdLastModifiedDate.put(workId, lastModifiedDate); + } + vn.toElement(VTDNav.PARENT); + } + } + return workIdLastModifiedDate; + } + + public static AuthorSummary VTDParseAuthorSummary(byte[] bytes) + throws VtdException, ParseException { + final VTDGen vg = new VTDGen(); + vg.setDoc(bytes); + vg.parse(true); + final VTDNav vn = vg.getNav(); + final AutoPilot ap = new AutoPilot(vn); + ap.declareXPathNameSpace(NS_COMMON, NS_COMMON_URL); + ap.declareXPathNameSpace(NS_PERSON, NS_PERSON_URL); + ap.declareXPathNameSpace(NS_DETAILS, NS_DETAILS_URL); + ap.declareXPathNameSpace(NS_OTHER, NS_OTHER_URL); + ap.declareXPathNameSpace(NS_RECORD, NS_RECORD_URL); + ap.declareXPathNameSpace(NS_ERROR, NS_ERROR_URL); + ap.declareXPathNameSpace(NS_HISTORY, NS_HISTORY_URL); + + AuthorData authorData = retrieveAuthorData(ap, vn, bytes); + AuthorHistory authorHistory = retrieveAuthorHistory(ap, vn, bytes); + AuthorSummary authorSummary = new AuthorSummary(); + authorSummary.setAuthorData(authorData); + authorSummary.setAuthorHistory(authorHistory); + return authorSummary; + } + + private static AuthorData retrieveAuthorData(AutoPilot ap, VTDNav vn, byte[] bytes) + throws VtdException { + AuthorData authorData = new AuthorData(); + final List errors = VtdUtilityParser.getTextValue(ap, vn, "//error:response-code"); + if (!errors.isEmpty()) { + authorData.setErrorCode(errors.get(0)); + return authorData; + } + + List recordNodes = VtdUtilityParser + .getTextValuesWithAttributes( + ap, vn, "//record:record", Arrays.asList("path")); + if (!recordNodes.isEmpty()) { + final String oid = (recordNodes.get(0).getAttributes().get("path")).substring(1); + authorData.setOid(oid); + } else { + return null; + } + + final List names = VtdUtilityParser.getTextValue(ap, vn, "//personal-details:given-names"); + if (!names.isEmpty()) { + authorData.setName(names.get(0)); + } + + final List surnames = VtdUtilityParser.getTextValue(ap, vn, "//personal-details:family-name"); + if (!surnames.isEmpty()) { + authorData.setSurname(surnames.get(0)); + } + + final List creditNames = VtdUtilityParser.getTextValue(ap, vn, "//personal-details:credit-name"); + if (!creditNames.isEmpty()) { + authorData.setCreditName(creditNames.get(0)); + } + + final List otherNames = VtdUtilityParser.getTextValue(ap, vn, "//other-name:content"); + if (!otherNames.isEmpty()) { + authorData.setOtherNames(otherNames); + } + return authorData; + } + + private static AuthorHistory retrieveAuthorHistory(AutoPilot ap, VTDNav vn, byte[] bytes) + throws VtdException { + AuthorHistory authorHistory = new AuthorHistory(); + final String creationMethod = VtdUtilityParser.getSingleValue(ap, vn, "//history:creation-method"); + if (StringUtils.isNoneBlank(creationMethod)) { + authorHistory.setCreationMethod(creationMethod); + } + + final String completionDate = VtdUtilityParser.getSingleValue(ap, vn, "//history:completion-date"); + if (StringUtils.isNoneBlank(completionDate)) { + authorHistory.setCompletionDate(completionDate); + } + + final String submissionDate = VtdUtilityParser.getSingleValue(ap, vn, "//history:submission-date"); + if (StringUtils.isNoneBlank(submissionDate)) { + authorHistory.setSubmissionDate(submissionDate); + } + + final String claimed = VtdUtilityParser.getSingleValue(ap, vn, "//history:claimed"); + if (StringUtils.isNoneBlank(claimed)) { + authorHistory.setClaimed(Boolean.parseBoolean(claimed)); + } + + final String verifiedEmail = VtdUtilityParser.getSingleValue(ap, vn, "//history:verified-email"); + if (StringUtils.isNoneBlank(verifiedEmail)) { + authorHistory.setVerifiedEmail(Boolean.parseBoolean(verifiedEmail)); + } + + final String verifiedPrimaryEmail = VtdUtilityParser.getSingleValue(ap, vn, "//history:verified-primary-email"); + if (StringUtils.isNoneBlank(verifiedPrimaryEmail)) { + authorHistory.setVerifiedPrimaryEmail(Boolean.parseBoolean(verifiedPrimaryEmail)); + } + + final String deactivationDate = VtdUtilityParser.getSingleValue(ap, vn, "//history:deactivation-date"); + if (StringUtils.isNoneBlank(deactivationDate)) { + authorHistory.setDeactivationDate(deactivationDate); + } + + final String lastModifiedDate = VtdUtilityParser + .getSingleValue(ap, vn, "//history:history/common:last-modified-date"); + if (StringUtils.isNoneBlank(lastModifiedDate)) { + authorHistory.setLastModifiedDate(lastModifiedDate); + } + return authorHistory; + } } diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/ActivitiesDumpReader.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/ActivitiesDumpReader.java index c2cfafd87..04a3389ed 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/ActivitiesDumpReader.java +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/ActivitiesDumpReader.java @@ -19,8 +19,8 @@ import org.apache.hadoop.io.compress.CompressionCodec; import org.apache.hadoop.io.compress.CompressionCodecFactory; import org.mortbay.log.Log; +import eu.dnetlib.dhp.schema.orcid.WorkDetail; import eu.dnetlib.doiboost.orcid.json.JsonHelper; -import eu.dnetlib.doiboost.orcidnodoi.model.WorkDataNoDoi; import eu.dnetlib.doiboost.orcidnodoi.xml.XMLRecordParserNoDoi; /** @@ -87,29 +87,29 @@ public class ActivitiesDumpReader { while ((line = br.readLine()) != null) { buffer.append(line); } - WorkDataNoDoi workDataNoDoi = XMLRecordParserNoDoi + WorkDetail workDetail = XMLRecordParserNoDoi .VTDParseWorkData(buffer.toString().getBytes()); - if (workDataNoDoi != null) { - if (workDataNoDoi.getErrorCode() != null) { + if (workDetail != null) { + if (workDetail.getErrorCode() != null) { errorFromOrcidFound += 1; Log .debug( "error from Orcid with code " - + workDataNoDoi.getErrorCode() + + workDetail.getErrorCode() + " for entry " + entry.getName()); continue; } - boolean isDoiFound = workDataNoDoi + boolean isDoiFound = workDetail .getExtIds() .stream() .filter(e -> e.getType() != null) .anyMatch(e -> e.getType().equals("doi")); if (!isDoiFound) { - String jsonData = JsonHelper.createOidWork(workDataNoDoi); - Log.debug("oid: " + workDataNoDoi.getOid() + " data: " + jsonData); + String jsonData = JsonHelper.createOidWork(workDetail); + Log.debug("oid: " + workDetail.getOid() + " data: " + jsonData); - final Text key = new Text(workDataNoDoi.getOid()); + final Text key = new Text(workDetail.getOid()); final Text value = new Text(jsonData); try { diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/SparkGenEnrichedOrcidWorks.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/SparkGenEnrichedOrcidWorks.java index a92d534d8..5bcec7224 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/SparkGenEnrichedOrcidWorks.java +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/SparkGenEnrichedOrcidWorks.java @@ -4,10 +4,12 @@ package eu.dnetlib.doiboost.orcidnodoi; import static eu.dnetlib.dhp.common.SparkSessionSupport.runWithSparkSession; import java.io.IOException; +import java.util.List; import java.util.Objects; import java.util.Optional; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat; import org.apache.spark.SparkConf; @@ -18,6 +20,7 @@ import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoders; import org.apache.spark.util.LongAccumulator; +import org.mortbay.log.Log; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,14 +33,17 @@ import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.schema.action.AtomicAction; import eu.dnetlib.dhp.schema.oaf.Publication; import eu.dnetlib.dhp.schema.orcid.AuthorData; +import eu.dnetlib.dhp.schema.orcid.AuthorSummary; +import eu.dnetlib.dhp.schema.orcid.Work; +import eu.dnetlib.dhp.schema.orcid.WorkDetail; import eu.dnetlib.doiboost.orcid.json.JsonHelper; -import eu.dnetlib.doiboost.orcidnodoi.model.WorkDataNoDoi; +import eu.dnetlib.doiboost.orcid.util.HDFSUtil; import eu.dnetlib.doiboost.orcidnodoi.oaf.PublicationToOaf; import eu.dnetlib.doiboost.orcidnodoi.similarity.AuthorMatcher; import scala.Tuple2; /** - * This spark job generates one parquet file, containing orcid publications dataset + * This spark job generates orcid publications no doi dataset */ public class SparkGenEnrichedOrcidWorks { @@ -53,47 +59,65 @@ public class SparkGenEnrichedOrcidWorks { .toString( SparkGenEnrichedOrcidWorks.class .getResourceAsStream( - "/eu/dnetlib/dhp/doiboost/gen_enriched_orcid_works_parameters.json"))); + "/eu/dnetlib/dhp/doiboost/gen_orcid-no-doi_params.json"))); parser.parseArgument(args); Boolean isSparkSessionManaged = Optional .ofNullable(parser.get("isSparkSessionManaged")) .map(Boolean::valueOf) .orElse(Boolean.TRUE); + final String hdfsServerUri = parser.get("hdfsServerUri"); final String workingPath = parser.get("workingPath"); final String outputEnrichedWorksPath = parser.get("outputEnrichedWorksPath"); - final String outputWorksPath = parser.get("outputWorksPath"); - final String hdfsServerUri = parser.get("hdfsServerUri"); + final String orcidDataFolder = parser.get("orcidDataFolder"); SparkConf conf = new SparkConf(); runWithSparkSession( conf, isSparkSessionManaged, spark -> { + String lastUpdate = HDFSUtil.readFromTextFile(hdfsServerUri, workingPath, "last_update.txt"); + if (StringUtils.isBlank(lastUpdate)) { + throw new RuntimeException("last update info not found"); + } + final String dateOfCollection = lastUpdate.substring(0, 10); JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); - JavaPairRDD summariesRDD = sc - .sequenceFile(workingPath + "authors/authors.seq", Text.class, Text.class); - Dataset summariesDataset = spark + Dataset authorDataset = spark .createDataset( - summariesRDD.map(seq -> loadAuthorFromJson(seq._1(), seq._2())).rdd(), + sc + .textFile(workingPath.concat(orcidDataFolder).concat("/authors/*")) + .map(item -> OBJECT_MAPPER.readValue(item, AuthorSummary.class)) + .filter(authorSummary -> authorSummary.getAuthorData() != null) + .map(authorSummary -> authorSummary.getAuthorData()) + .rdd(), Encoders.bean(AuthorData.class)); - logger.info("Authors data loaded: " + summariesDataset.count()); + logger.info("Authors data loaded: " + authorDataset.count()); - JavaPairRDD activitiesRDD = sc - .sequenceFile(workingPath + outputWorksPath + "*.seq", Text.class, Text.class); - Dataset activitiesDataset = spark + Dataset workDataset = spark .createDataset( - activitiesRDD.map(seq -> loadWorkFromJson(seq._1(), seq._2())).rdd(), - Encoders.bean(WorkDataNoDoi.class)); - logger.info("Works data loaded: " + activitiesDataset.count()); + sc + .textFile(workingPath.concat(orcidDataFolder).concat("/works/*")) + .map(item -> OBJECT_MAPPER.readValue(item, Work.class)) + .filter(work -> work.getWorkDetail() != null) + .map(work -> work.getWorkDetail()) + .filter(work -> work.getErrorCode() == null) + .filter( + work -> work + .getExtIds() + .stream() + .filter(e -> e.getType() != null) + .noneMatch(e -> e.getType().equalsIgnoreCase("doi"))) + .rdd(), + Encoders.bean(WorkDetail.class)); + logger.info("Works data loaded: " + workDataset.count()); - JavaRDD> enrichedWorksRDD = activitiesDataset + JavaRDD> enrichedWorksRDD = workDataset .joinWith( - summariesDataset, - activitiesDataset.col("oid").equalTo(summariesDataset.col("oid")), "inner") + authorDataset, + workDataset.col("oid").equalTo(authorDataset.col("oid")), "inner") .map( - (MapFunction, Tuple2>) value -> { - WorkDataNoDoi w = value._1; + (MapFunction, Tuple2>) value -> { + WorkDetail w = value._1; AuthorData a = value._2; AuthorMatcher.match(a, w.getContributors()); return new Tuple2<>(a.getOid(), JsonHelper.createOidWork(w)); @@ -113,13 +137,25 @@ public class SparkGenEnrichedOrcidWorks { .sparkContext() .longAccumulator("errorsNotFoundAuthors"); final LongAccumulator errorsInvalidType = spark.sparkContext().longAccumulator("errorsInvalidType"); + final LongAccumulator otherTypeFound = spark.sparkContext().longAccumulator("otherTypeFound"); + final LongAccumulator deactivatedAcc = spark.sparkContext().longAccumulator("deactivated_found"); + final LongAccumulator titleNotProvidedAcc = spark + .sparkContext() + .longAccumulator("Title_not_provided_found"); + final LongAccumulator noUrlAcc = spark.sparkContext().longAccumulator("no_url_found"); + final PublicationToOaf publicationToOaf = new PublicationToOaf( parsedPublications, enrichedPublications, errorsGeneric, errorsInvalidTitle, errorsNotFoundAuthors, - errorsInvalidType); + errorsInvalidType, + otherTypeFound, + deactivatedAcc, + titleNotProvidedAcc, + noUrlAcc, + dateOfCollection); JavaRDD oafPublicationRDD = enrichedWorksRDD .map( e -> { @@ -148,33 +184,10 @@ public class SparkGenEnrichedOrcidWorks { logger.info("errorsInvalidTitle: " + errorsInvalidTitle.value().toString()); logger.info("errorsNotFoundAuthors: " + errorsNotFoundAuthors.value().toString()); logger.info("errorsInvalidType: " + errorsInvalidType.value().toString()); + logger.info("otherTypeFound: " + otherTypeFound.value().toString()); + logger.info("deactivatedAcc: " + deactivatedAcc.value().toString()); + logger.info("titleNotProvidedAcc: " + titleNotProvidedAcc.value().toString()); + logger.info("noUrlAcc: " + noUrlAcc.value().toString()); }); } - - private static AuthorData loadAuthorFromJson(Text orcidId, Text json) { - AuthorData authorData = new AuthorData(); - authorData.setOid(orcidId.toString()); - JsonElement jElement = new JsonParser().parse(json.toString()); - authorData.setName(getJsonValue(jElement, "name")); - authorData.setSurname(getJsonValue(jElement, "surname")); - authorData.setCreditName(getJsonValue(jElement, "creditname")); - return authorData; - } - - private static WorkDataNoDoi loadWorkFromJson(Text orcidId, Text json) { - - WorkDataNoDoi workData = new Gson().fromJson(json.toString(), WorkDataNoDoi.class); - return workData; - } - - private static String getJsonValue(JsonElement jElement, String property) { - if (jElement.getAsJsonObject().has(property)) { - JsonElement name = null; - name = jElement.getAsJsonObject().get(property); - if (name != null && !name.isJsonNull()) { - return name.getAsString(); - } - } - return new String(""); - } } diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/json/JsonWriter.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/json/JsonWriter.java index 982fb6316..a89bbc279 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/json/JsonWriter.java +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/json/JsonWriter.java @@ -22,6 +22,10 @@ public class JsonWriter { return OBJECT_MAPPER.writeValueAsString(authorData); } + public static String create(Object obj) throws JsonProcessingException { + return OBJECT_MAPPER.writeValueAsString(obj); + } + public static String create(WorkData workData) { JsonObject work = new JsonObject(); work.addProperty("oid", workData.getOid()); diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java index 3b8c74062..050f4d327 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java @@ -18,7 +18,6 @@ import com.google.gson.*; import eu.dnetlib.dhp.common.PacePerson; import eu.dnetlib.dhp.schema.common.ModelConstants; import eu.dnetlib.dhp.schema.oaf.*; -import eu.dnetlib.dhp.schema.scholexplorer.OafUtils; import eu.dnetlib.dhp.utils.DHPUtils; import eu.dnetlib.doiboost.orcidnodoi.util.DumpToActionsUtility; import eu.dnetlib.doiboost.orcidnodoi.util.Pair; @@ -26,21 +25,28 @@ import eu.dnetlib.doiboost.orcidnodoi.util.Pair; /** * This class converts an orcid publication from json format to oaf */ + public class PublicationToOaf implements Serializable { static Logger logger = LoggerFactory.getLogger(PublicationToOaf.class); - public static final String ORCID = StringUtils.upperCase(ModelConstants.ORCID); public final static String orcidPREFIX = "orcid_______"; public static final String OPENAIRE_PREFIX = "openaire____"; public static final String SEPARATOR = "::"; + public static final String DEACTIVATED_NAME = "Given Names Deactivated"; + public static final String DEACTIVATED_SURNAME = "Family Name Deactivated"; + private String dateOfCollection = ""; private final LongAccumulator parsedPublications; private final LongAccumulator enrichedPublications; private final LongAccumulator errorsGeneric; private final LongAccumulator errorsInvalidTitle; private final LongAccumulator errorsNotFoundAuthors; private final LongAccumulator errorsInvalidType; + private final LongAccumulator otherTypeFound; + private final LongAccumulator deactivatedAcc; + private final LongAccumulator titleNotProvidedAcc; + private final LongAccumulator noUrlAcc; public PublicationToOaf( LongAccumulator parsedPublications, @@ -48,13 +54,23 @@ public class PublicationToOaf implements Serializable { LongAccumulator errorsGeneric, LongAccumulator errorsInvalidTitle, LongAccumulator errorsNotFoundAuthors, - LongAccumulator errorsInvalidType) { + LongAccumulator errorsInvalidType, + LongAccumulator otherTypeFound, + LongAccumulator deactivatedAcc, + LongAccumulator titleNotProvidedAcc, + LongAccumulator noUrlAcc, + String dateOfCollection) { this.parsedPublications = parsedPublications; this.enrichedPublications = enrichedPublications; this.errorsGeneric = errorsGeneric; this.errorsInvalidTitle = errorsInvalidTitle; this.errorsNotFoundAuthors = errorsNotFoundAuthors; this.errorsInvalidType = errorsInvalidType; + this.otherTypeFound = otherTypeFound; + this.deactivatedAcc = deactivatedAcc; + this.titleNotProvidedAcc = titleNotProvidedAcc; + this.noUrlAcc = noUrlAcc; + this.dateOfCollection = dateOfCollection; } public PublicationToOaf() { @@ -64,12 +80,19 @@ public class PublicationToOaf implements Serializable { this.errorsInvalidTitle = null; this.errorsNotFoundAuthors = null; this.errorsInvalidType = null; + this.otherTypeFound = null; + this.deactivatedAcc = null; + this.titleNotProvidedAcc = null; + this.noUrlAcc = null; + this.dateOfCollection = null; } private static Map> datasources = new HashMap>() { { - put(ORCID.toLowerCase(), new Pair<>(ORCID, OPENAIRE_PREFIX + SEPARATOR + ModelConstants.ORCID)); + put( + ModelConstants.ORCID, + new Pair<>(ModelConstants.ORCID.toUpperCase(), OPENAIRE_PREFIX + SEPARATOR + "orcid")); } }; @@ -79,10 +102,10 @@ public class PublicationToOaf implements Serializable { { put("ark".toLowerCase(), new Pair<>("ark", "ark")); - put("arxiv".toLowerCase(), new Pair<>("arxiv", "arXiv")); - put("pmc".toLowerCase(), new Pair<>("pmc", "pmc")); - put("pmid".toLowerCase(), new Pair<>("pmid", "pmid")); - put("source-work-id".toLowerCase(), new Pair<>("orcidworkid", "orcidworkid")); + put("arxiv".toLowerCase(), new Pair<>("arXiv", "arXiv")); + put("pmc".toLowerCase(), new Pair<>("pmc", "PubMed Central ID")); + put("pmid".toLowerCase(), new Pair<>("pmid", "PubMed ID")); + put("source-work-id".toLowerCase(), new Pair<>("orcidworkid", "orcid workid")); put("urn".toLowerCase(), new Pair<>("urn", "urn")); } }; @@ -102,21 +125,15 @@ public class PublicationToOaf implements Serializable { } } + public static final String PID_TYPES = "dnet:pid_types"; + public Oaf generatePublicationActionsFromJson(final String json) { - try { - if (parsedPublications != null) { - parsedPublications.add(1); - } - JsonElement jElement = new JsonParser().parse(json); - JsonObject jObject = jElement.getAsJsonObject(); - return generatePublicationActionsFromDump(jObject); - } catch (Throwable t) { - logger.error("creating publication: " + t.getMessage()); - if (errorsGeneric != null) { - errorsGeneric.add(1); - } - return null; + if (parsedPublications != null) { + parsedPublications.add(1); } + JsonElement jElement = new JsonParser().parse(json); + JsonObject jObject = jElement.getAsJsonObject(); + return generatePublicationActionsFromDump(jObject); } public Oaf generatePublicationActionsFromDump(final JsonObject rootElement) { @@ -142,7 +159,7 @@ public class PublicationToOaf implements Serializable { publication.setLastupdatetimestamp(new Date().getTime()); - publication.setDateofcollection("2020-10-14"); + publication.setDateofcollection(dateOfCollection); publication.setDateoftransformation(DumpToActionsUtility.now_ISO8601()); // Adding external ids @@ -150,8 +167,8 @@ public class PublicationToOaf implements Serializable { .keySet() .stream() .forEach(jsonExtId -> { - final String classid = externalIds.get(jsonExtId.toLowerCase()).getValue(); - final String classname = externalIds.get(jsonExtId.toLowerCase()).getKey(); + final String classid = externalIds.get(jsonExtId.toLowerCase()).getKey(); + final String classname = externalIds.get(jsonExtId.toLowerCase()).getValue(); final String extId = getStringValue(rootElement, jsonExtId); if (StringUtils.isNotBlank(extId)) { publication @@ -182,11 +199,19 @@ public class PublicationToOaf implements Serializable { } return null; } + if (titles.stream().filter(t -> (t != null && t.equals("Title Not Supplied"))).count() > 0) { + if (titleNotProvidedAcc != null) { + titleNotProvidedAcc.add(1); + } + return null; + } publication .setTitle( titles .stream() - .map(t -> mapStructuredProperty(t, ModelConstants.MAIN_TITLE_QUALIFIER, null)) + .map(t -> { + return mapStructuredProperty(t, ModelConstants.MAIN_TITLE_QUALIFIER, null); + }) .filter(s -> s != null) .collect(Collectors.toList())); // Adding identifier @@ -216,8 +241,23 @@ public class PublicationToOaf implements Serializable { mapQualifier( type, type, ModelConstants.DNET_DATA_CITE_RESOURCE, ModelConstants.DNET_DATA_CITE_RESOURCE)); + Map publicationType = typologiesMapping.get(type); + if ((publicationType == null || publicationType.isEmpty()) && errorsInvalidType != null) { + errorsInvalidType.add(1); + logger.error("publication_type_not_found: " + type); + return null; + } + final String typeValue = typologiesMapping.get(type).get("value"); cobjValue = typologiesMapping.get(type).get("cobj"); + // this dataset must contain only publication + if (cobjValue.equals("0020")) { + if (otherTypeFound != null) { + otherTypeFound.add(1); + } + return null; + } + final Instance instance = new Instance(); // Adding hostedby @@ -228,9 +268,14 @@ public class PublicationToOaf implements Serializable { if (urls != null && !urls.isEmpty()) { instance.setUrl(urls); } else { - dataInfo.setInvisible(true); + if (noUrlAcc != null) { + noUrlAcc.add(1); + } + return null; } + dataInfo.setInvisible(true); + final String pubDate = getPublicationDate(rootElement, "publicationDates"); if (StringUtils.isNotBlank(pubDate)) { instance.setDateofacceptance(mapStringField(pubDate, null)); @@ -241,11 +286,9 @@ public class PublicationToOaf implements Serializable { // Adding accessright instance .setAccessright( - OafUtils - .createAccessRight( - ModelConstants.UNKNOWN, - ModelConstants.UNKNOWN, - ModelConstants.DNET_ACCESS_MODES, + OafMapperUtils + .accessRight( + ModelConstants.UNKNOWN, "Unknown", ModelConstants.DNET_ACCESS_MODES, ModelConstants.DNET_ACCESS_MODES)); // Adding type @@ -266,12 +309,28 @@ public class PublicationToOaf implements Serializable { // Adding authors final List authors = createAuthors(rootElement); if (authors != null && authors.size() > 0) { - publication.setAuthor(authors); - } else { - if (errorsNotFoundAuthors != null) { - errorsNotFoundAuthors.add(1); + if (authors.stream().filter(a -> { + return ((Objects.nonNull(a.getName()) && a.getName().equals(DEACTIVATED_NAME)) || + (Objects.nonNull(a.getSurname()) && a.getSurname().equals(DEACTIVATED_SURNAME))); + }).count() > 0) { + if (deactivatedAcc != null) { + deactivatedAcc.add(1); + } + return null; + } else { + publication.setAuthor(authors); + } + } else { + if (authors == null) { + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + String json = gson.toJson(rootElement); + throw new RuntimeException("not_valid_authors: " + json); + } else { + if (errorsNotFoundAuthors != null) { + errorsNotFoundAuthors.add(1); + } + return null; } - return null; } String classValue = getDefaultResulttype(cobjValue); publication @@ -518,36 +577,33 @@ public class PublicationToOaf implements Serializable { private KeyValue createCollectedFrom() { KeyValue cf = new KeyValue(); - cf.setValue(ORCID); + cf.setValue(ModelConstants.ORCID.toUpperCase()); cf.setKey("10|" + OPENAIRE_PREFIX + SEPARATOR + "806360c771262b4d6770e7cdf04b5c5a"); return cf; } private KeyValue createHostedBy() { - KeyValue hb = new KeyValue(); - hb.setValue("Unknown Repository"); - hb.setKey("10|" + OPENAIRE_PREFIX + SEPARATOR + "55045bd2a65019fd8e6741a755395c8c"); - return hb; + return ModelConstants.UNKNOWN_REPOSITORY; } private StructuredProperty mapAuthorId(String orcidId) { final StructuredProperty sp = new StructuredProperty(); sp.setValue(orcidId); final Qualifier q = new Qualifier(); - q.setClassid(ORCID.toLowerCase()); - q.setClassname(ORCID.toLowerCase()); + q.setClassid(ModelConstants.ORCID); + q.setClassname(ModelConstants.ORCID_CLASSNAME); q.setSchemeid(ModelConstants.DNET_PID_TYPES); q.setSchemename(ModelConstants.DNET_PID_TYPES); sp.setQualifier(q); final DataInfo dataInfo = new DataInfo(); dataInfo.setDeletedbyinference(false); dataInfo.setInferred(false); - dataInfo.setTrust("0.9"); + dataInfo.setTrust("0.91"); dataInfo .setProvenanceaction( mapQualifier( - "sysimport:crosswalk:entityregistry", - "Harvested", + ModelConstants.SYSIMPORT_CROSSWALK_ENTITYREGISTRY, + ModelConstants.HARVESTED, ModelConstants.DNET_PROVENANCE_ACTIONS, ModelConstants.DNET_PROVENANCE_ACTIONS)); sp.setDataInfo(dataInfo); diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java.rej b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java.rej new file mode 100644 index 000000000..76b63a93d --- /dev/null +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java.rej @@ -0,0 +1,77 @@ +diff a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java (rejected hunks) +@@ -30,11 +30,11 @@ public class PublicationToOaf implements Serializable { + + static Logger logger = LoggerFactory.getLogger(PublicationToOaf.class); + +- public static final String ORCID = "ORCID"; +- public static final String ORCID_PID_TYPE_CLASSNAME = "Open Researcher and Contributor ID"; + public final static String orcidPREFIX = "orcid_______"; + public static final String OPENAIRE_PREFIX = "openaire____"; + public static final String SEPARATOR = "::"; ++ public static final String DEACTIVATED_NAME = "Given Names Deactivated"; ++ public static final String DEACTIVATED_SURNAME = "Family Name Deactivated"; + + private String dateOfCollection = ""; + private final LongAccumulator parsedPublications; +@@ -72,13 +81,18 @@ public class PublicationToOaf implements Serializable { + this.errorsNotFoundAuthors = null; + this.errorsInvalidType = null; + this.otherTypeFound = null; ++ this.deactivatedAcc = null; ++ this.titleNotProvidedAcc = null; ++ this.noUrlAcc = null; + this.dateOfCollection = null; + } + + private static Map> datasources = new HashMap>() { + + { +- put(ORCID.toLowerCase(), new Pair<>(ORCID, OPENAIRE_PREFIX + SEPARATOR + "orcid")); ++ put( ++ ModelConstants.ORCID, ++ new Pair<>(ModelConstants.ORCID.toUpperCase(), OPENAIRE_PREFIX + SEPARATOR + "orcid")); + + } + }; +@@ -183,6 +197,12 @@ public class PublicationToOaf implements Serializable { + } + return null; + } ++ if (titles.stream().filter(t -> (t != null && t.equals("Title Not Supplied"))).count() > 0) { ++ if (titleNotProvidedAcc != null) { ++ titleNotProvidedAcc.add(1); ++ } ++ return null; ++ } + Qualifier q = mapQualifier("main title", "main title", "dnet:dataCite_title", "dnet:dataCite_title"); + publication + .setTitle( +@@ -527,24 +562,21 @@ public class PublicationToOaf implements Serializable { + + private KeyValue createCollectedFrom() { + KeyValue cf = new KeyValue(); +- cf.setValue(ORCID); ++ cf.setValue(ModelConstants.ORCID.toUpperCase()); + cf.setKey("10|" + OPENAIRE_PREFIX + SEPARATOR + "806360c771262b4d6770e7cdf04b5c5a"); + return cf; + } + + private KeyValue createHostedBy() { +- KeyValue hb = new KeyValue(); +- hb.setValue("Unknown Repository"); +- hb.setKey("10|" + OPENAIRE_PREFIX + SEPARATOR + "55045bd2a65019fd8e6741a755395c8c"); +- return hb; ++ return ModelConstants.UNKNOWN_REPOSITORY; + } + + private StructuredProperty mapAuthorId(String orcidId) { + final StructuredProperty sp = new StructuredProperty(); + sp.setValue(orcidId); + final Qualifier q = new Qualifier(); +- q.setClassid(ORCID.toLowerCase()); +- q.setClassname(ORCID_PID_TYPE_CLASSNAME); ++ q.setClassid(ModelConstants.ORCID); ++ q.setClassname(ModelConstants.ORCID_CLASSNAME); + q.setSchemeid(ModelConstants.DNET_PID_TYPES); + q.setSchemename(ModelConstants.DNET_PID_TYPES); + sp.setQualifier(q); diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/similarity/AuthorMatcher.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/similarity/AuthorMatcher.java index c0f617868..e36ed3bbf 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/similarity/AuthorMatcher.java +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/similarity/AuthorMatcher.java @@ -19,8 +19,8 @@ import com.ximpleware.XPathParseException; import eu.dnetlib.dhp.parser.utility.VtdException; import eu.dnetlib.dhp.schema.orcid.AuthorData; -import eu.dnetlib.doiboost.orcidnodoi.model.Contributor; -import eu.dnetlib.doiboost.orcidnodoi.model.WorkDataNoDoi; +import eu.dnetlib.dhp.schema.orcid.Contributor; +import eu.dnetlib.dhp.schema.orcid.WorkDetail; /** * This class is used for searching from a list of publication contributors a @@ -209,7 +209,7 @@ public class AuthorMatcher { } } - private static String toJson(WorkDataNoDoi work) { + private static String toJson(WorkDetail work) { GsonBuilder builder = new GsonBuilder(); Gson gson = builder.create(); return gson.toJson(work); diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/xml/XMLRecordParserNoDoi.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/xml/XMLRecordParserNoDoi.java index f4b093402..15cd4f268 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/xml/XMLRecordParserNoDoi.java +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/xml/XMLRecordParserNoDoi.java @@ -12,10 +12,10 @@ import com.ximpleware.*; import eu.dnetlib.dhp.parser.utility.VtdException; import eu.dnetlib.dhp.parser.utility.VtdUtilityParser; -import eu.dnetlib.doiboost.orcidnodoi.model.Contributor; -import eu.dnetlib.doiboost.orcidnodoi.model.ExternalId; -import eu.dnetlib.doiboost.orcidnodoi.model.PublicationDate; -import eu.dnetlib.doiboost.orcidnodoi.model.WorkDataNoDoi; +import eu.dnetlib.dhp.schema.orcid.Contributor; +import eu.dnetlib.dhp.schema.orcid.ExternalId; +import eu.dnetlib.dhp.schema.orcid.PublicationDate; +import eu.dnetlib.dhp.schema.orcid.WorkDetail; /** * This class is used for parsing xml data with vtd parser @@ -42,7 +42,7 @@ public class XMLRecordParserNoDoi { private static final String NS_ERROR = "error"; - public static WorkDataNoDoi VTDParseWorkData(byte[] bytes) + public static WorkDetail VTDParseWorkData(byte[] bytes) throws VtdException, EncodingException, EOFException, EntityException, ParseException, XPathParseException, NavException, XPathEvalException { final VTDGen vg = new VTDGen(); @@ -54,7 +54,7 @@ public class XMLRecordParserNoDoi { ap.declareXPathNameSpace(NS_WORK, NS_WORK_URL); ap.declareXPathNameSpace(NS_ERROR, NS_ERROR_URL); - WorkDataNoDoi workData = new WorkDataNoDoi(); + WorkDetail workData = new WorkDetail(); final List errors = VtdUtilityParser.getTextValue(ap, vn, "//error:response-code"); if (!errors.isEmpty()) { workData.setErrorCode(errors.get(0)); diff --git a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/gen_doi_author_list_orcid_parameters.json b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/gen_doi_author_list_orcid_parameters.json index b894177b3..41c1a2a7d 100644 --- a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/gen_doi_author_list_orcid_parameters.json +++ b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/gen_doi_author_list_orcid_parameters.json @@ -1,3 +1,5 @@ [{"paramName":"w", "paramLongName":"workingPath", "paramDescription": "the working path", "paramRequired": true}, + {"paramName":"a", "paramLongName":"authorsPath", "paramDescription": "the path of the authors seq file", "paramRequired": true}, + {"paramName":"xw", "paramLongName":"xmlWorksPath", "paramDescription": "the path of the works xml seq file", "paramRequired": true}, {"paramName":"o", "paramLongName":"outputDoiAuthorListPath", "paramDescription": "the relative folder of the sequencial file to write the data", "paramRequired": true} ] \ No newline at end of file diff --git a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/gen_enriched_orcid_works_parameters.json b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/gen_orcid-no-doi_params.json similarity index 57% rename from dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/gen_enriched_orcid_works_parameters.json rename to dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/gen_orcid-no-doi_params.json index c3a8f92ec..3456329b1 100644 --- a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/gen_enriched_orcid_works_parameters.json +++ b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/gen_orcid-no-doi_params.json @@ -1,7 +1,6 @@ [ {"paramName":"n", "paramLongName":"hdfsServerUri", "paramDescription": "the server uri", "paramRequired": true}, {"paramName":"w", "paramLongName":"workingPath", "paramDescription": "the default work path", "paramRequired": true}, - {"paramName":"f", "paramLongName":"activitiesFileNameTarGz", "paramDescription": "the name of the activities orcid file", "paramRequired": true}, - {"paramName":"ow", "paramLongName":"outputWorksPath", "paramDescription": "the relative folder of the sequencial file to write", "paramRequired": true}, + {"paramName":"i", "paramLongName":"orcidDataFolder", "paramDescription": "the folder of orcid data", "paramRequired": true}, {"paramName":"oew", "paramLongName":"outputEnrichedWorksPath", "paramDescription": "the relative folder of the sequencial file to write the data", "paramRequired": true} ] \ No newline at end of file diff --git a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_doi_author_list/oozie_app/config-default.xml b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_doi_author_list/oozie_app/config-default.xml deleted file mode 100644 index 3726022cb..000000000 --- a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_doi_author_list/oozie_app/config-default.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - jobTracker - hadoop-rm3.garr-pa1.d4science.org:8032 - - - nameNode - hdfs://hadoop-rm1.garr-pa1.d4science.org:8020 - - - queueName - default - - - oozie.action.sharelib.for.spark - spark2 - - \ No newline at end of file diff --git a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_doi_author_list/oozie_app/workflow.xml b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_doi_author_list/oozie_app/workflow.xml index 21d092a83..133a6f4bd 100644 --- a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_doi_author_list/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_doi_author_list/oozie_app/workflow.xml @@ -1,55 +1,99 @@ - - + + + + sparkDriverMemory + memory for driver process + + + sparkExecutorMemory + memory for individual executor + + + sparkExecutorCores + number of cores used by single executor + + + spark2MaxExecutors + 20 + + + oozieActionShareLibForSpark2 + oozie action sharelib for spark 2.* + + + spark2ExtraListeners + com.cloudera.spark.lineage.NavigatorAppListener + spark 2.* extra listeners classname + + + spark2SqlQueryExecutionListeners + com.cloudera.spark.lineage.NavigatorQueryListener + spark 2.* sql query execution listeners classname + + + spark2YarnHistoryServerAddress + spark 2.* yarn history server address + + + spark2EventLogDir + spark 2.* event log dir location + + + workingPath + the working dir base path + + + + + ${jobTracker} + ${nameNode} + - workingPath - the working dir base path + oozie.action.sharelib.for.spark + ${oozieActionShareLibForSpark2} - - sparkDriverMemory - memory for driver process - - - sparkExecutorMemory - memory for individual executor - - - sparkExecutorCores - number of cores used by single executor - - - - - - - - Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] - - - - - - - - - - - - - ${jobTracker} - ${nameNode} - yarn - cluster - Gen_Doi_Author_List - eu.dnetlib.doiboost.orcid.SparkGenerateDoiAuthorList - dhp-doiboost-1.2.1-SNAPSHOT.jar - --num-executors 10 --conf spark.yarn.jars="hdfs://hadoop-rm1.garr-pa1.d4science.org:8020/user/oozie/share/lib/lib_20180405103059/spark2" --executor-memory=${sparkExecutorMemory} --executor-cores=${sparkExecutorCores} --driver-memory=${sparkDriverMemory} - - -w${workingPath}/ - -odoi_author_list/ - - - - - - + + + + + + + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] + + + + + + + + + + + + + yarn-cluster + cluster + GenDoiAuthorList + eu.dnetlib.doiboost.orcid.SparkGenerateDoiAuthorList + dhp-doiboost-${projectVersion}.jar + + --executor-memory=${sparkExecutorMemory} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + --conf spark.dynamicAllocation.enabled=true + --conf spark.dynamicAllocation.maxExecutors=${spark2MaxExecutors} + + -w${workingPath}/ + -aauthors/authors.seq + -xwxml/works/*.seq + -odoi_author_list/ + + + + + + \ No newline at end of file diff --git a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_update/oozie_app/workflow.xml b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_update/oozie_app/workflow.xml new file mode 100644 index 000000000..135e6a4c8 --- /dev/null +++ b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_update/oozie_app/workflow.xml @@ -0,0 +1,163 @@ + + + + spark2MaxExecutors + 50 + + + sparkDriverMemory + memory for driver process + + + sparkExecutorMemory + memory for individual executor + + + sparkExecutorCores + number of cores used by single executor + + + oozieActionShareLibForSpark2 + oozie action sharelib for spark 2.* + + + spark2ExtraListeners + com.cloudera.spark.lineage.NavigatorAppListener + spark 2.* extra listeners classname + + + spark2SqlQueryExecutionListeners + com.cloudera.spark.lineage.NavigatorQueryListener + spark 2.* sql query execution listeners classname + + + spark2YarnHistoryServerAddress + spark 2.* yarn history server address + + + spark2EventLogDir + spark 2.* event log dir location + + + workingPath + the working dir base path + + + + + ${jobTracker} + ${nameNode} + + + oozie.action.sharelib.for.spark + ${oozieActionShareLibForSpark2} + + + + + + + + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] + + + + + + + + + + + + + + yarn-cluster + cluster + UpdateOrcidAuthors + eu.dnetlib.doiboost.orcid.SparkUpdateOrcidAuthors + dhp-doiboost-${projectVersion}.jar + + --conf spark.dynamicAllocation.enabled=true + --conf spark.dynamicAllocation.maxExecutors=${spark2MaxExecutors} + --executor-memory=${sparkExecutorMemory} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + + -w${workingPath}/ + -n${nameNode} + -f- + -o- + -t- + + + + + + + + yarn-cluster + cluster + UpdateOrcidWorks + eu.dnetlib.doiboost.orcid.SparkUpdateOrcidWorks + dhp-doiboost-${projectVersion}.jar + + --conf spark.dynamicAllocation.enabled=true + --conf spark.dynamicAllocation.maxExecutors=${spark2MaxExecutors} + --executor-memory=${sparkExecutorMemory} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + + -w${workingPath}/ + -n${nameNode} + -f- + -o- + -t- + + + + + + + + + + + + ${workingPath}/orcid_dataset/new_authors/* + ${workingPath}/orcid_dataset/authors + + + + + + + + + + + + ${workingPath}/orcid_dataset/new_works/* + ${workingPath}/orcid_dataset/works + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_updates_download/oozie_app/config-default.xml b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_updates_download/oozie_app/config-default.xml deleted file mode 100644 index 5621415d9..000000000 --- a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_updates_download/oozie_app/config-default.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - jobTracker - yarnRM - - - nameNode - hdfs://nameservice1 - - - oozie.action.sharelib.for.java - spark2 - - - oozie.launcher.mapreduce.user.classpath.first - true - - - oozie.launcher.mapreduce.map.java.opts - -Xmx4g - - \ No newline at end of file diff --git a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_updates_download/oozie_app/workflow.xml b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_updates_download/oozie_app/workflow.xml index b9383558c..fa161ad35 100644 --- a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_updates_download/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcid_updates_download/oozie_app/workflow.xml @@ -1,9 +1,25 @@ + + spark2UpdateStepMaxExecutors + 50 + workingPath the working dir base path + + oozie.action.sharelib.for.java + spark2 + + + oozie.launcher.mapreduce.user.classpath.first + true + + + oozie.launcher.mapreduce.map.java.opts + -Xmx4g + token access token @@ -30,7 +46,7 @@ number of cores used by single executor - spark2MaxExecutors + spark2DownloadingMaxExecutors 10 @@ -58,6 +74,8 @@ + ${jobTracker} + ${nameNode} oozie.action.sharelib.for.spark @@ -66,18 +84,16 @@ - - - + + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] - - + + - - + @@ -92,22 +108,7 @@ ${shell_cmd} - - - - - - - ${jobTracker} - ${nameNode} - eu.dnetlib.doiboost.orcid.OrcidDownloader - -w${workingPath}/ - -n${nameNode} - -flast_modified.csv.tar - -odownloads/ - -t${token} - - + @@ -133,7 +134,16 @@ -olast_modified.seq -t- - + + + + + + + + + + @@ -146,7 +156,7 @@ dhp-doiboost-${projectVersion}.jar --conf spark.dynamicAllocation.enabled=true - --conf spark.dynamicAllocation.maxExecutors=${spark2MaxExecutors} + --conf spark.dynamicAllocation.maxExecutors=${spark2DownloadingMaxExecutors} --executor-memory=${sparkExecutorMemory} --driver-memory=${sparkDriverMemory} --conf spark.extraListeners=${spark2ExtraListeners} @@ -160,9 +170,151 @@ -odownloads/updated_authors -t${token} + + + + + + + yarn-cluster + cluster + DownloadOrcidWorks + eu.dnetlib.doiboost.orcid.SparkDownloadOrcidWorks + dhp-doiboost-${projectVersion}.jar + + --conf spark.dynamicAllocation.enabled=true + --conf spark.dynamicAllocation.maxExecutors=${spark2DownloadingMaxExecutors} + --executor-memory=${sparkExecutorMemory} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + + -w${workingPath}/ + -n${nameNode} + -f- + -odownloads/updated_works + -t${token} + + + + + + + + yarn-cluster + cluster + UpdateOrcidAuthors + eu.dnetlib.doiboost.orcid.SparkUpdateOrcidAuthors + dhp-doiboost-${projectVersion}.jar + + --conf spark.dynamicAllocation.enabled=true + --conf spark.dynamicAllocation.maxExecutors=${spark2UpdateStepMaxExecutors} + --executor-memory=${sparkExecutorMemory} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + + -w${workingPath}/ + -n${nameNode} + -f- + -o- + -t- + + + + + + + + yarn-cluster + cluster + UpdateOrcidWorks + eu.dnetlib.doiboost.orcid.SparkUpdateOrcidWorks + dhp-doiboost-${projectVersion}.jar + + --conf spark.dynamicAllocation.enabled=true + --conf spark.dynamicAllocation.maxExecutors=${spark2UpdateStepMaxExecutors} + --executor-memory=${sparkExecutorMemory} + --driver-memory=${sparkDriverMemory} + --conf spark.extraListeners=${spark2ExtraListeners} + --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} + --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} + --conf spark.sql.queryExecutionListeners=${spark2SqlQueryExecutionListeners} + + -w${workingPath}/ + -n${nameNode} + -f- + -o- + -t- + + + + + + + + + + + + ${workingPath}/orcid_dataset/new_authors/* + ${workingPath}/orcid_dataset/authors + + + + + + + + + + + + ${workingPath}/orcid_dataset/new_works/* + ${workingPath}/orcid_dataset/works + + + + + + + + + + + + + + + + + + + + + ${workingPath}/orcid_dataset/authors/* + ${workingPath}/last_orcid_dataset/authors + + + + + + + + + + + + ${workingPath}/orcid_dataset/works/* + ${workingPath}/last_orcid_dataset/works + - + \ No newline at end of file diff --git a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcidnodoi/mappings/typologies.json b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcidnodoi/mappings/typologies.json index cb696f279..84b4f8418 100644 --- a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcidnodoi/mappings/typologies.json +++ b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcidnodoi/mappings/typologies.json @@ -1,19 +1,9 @@ { - "reference-entry": {"cobj":"0013", "value": "Part of book or chapter of book"}, "report": {"cobj":"0017", "value": "Report"}, - "dataset": {"cobj":"0021", "value": "Dataset"}, "journal-article": {"cobj":"0001", "value": "Article"}, - "reference-book": {"cobj":"0002", "value": "Book"}, "other": {"cobj":"0020", "value": "Other ORP type"}, - "proceedings-article": {"cobj":"0004", "value": "Conference object"}, - "standard": {"cobj":"0038", "value": "Other literature type"}, - "book-part": {"cobj":"0002", "value": "Book"}, - "monograph": {"cobj":"0002", "value": "Book"}, - "report-series": {"cobj":"0017", "value": "Report"}, "book": {"cobj":"0002", "value": "Book"}, "book-chapter": {"cobj":"0013", "value": "Part of book or chapter of book"}, - "peer-review": {"cobj":"0015", "value": "Review"}, - "book-section": {"cobj":"0013", "value": "Part of book or chapter of book"}, "book-review": {"cobj":"0015", "value": "Review"}, "conference-abstract": {"cobj":"0004", "value": "Conference object"}, "conference-paper": {"cobj":"0004", "value": "Conference object"}, @@ -21,7 +11,7 @@ "data-set": {"cobj":"0021", "value": "Dataset"}, "dictionary-entry": {"cobj":"0038", "value": "Other literature type"}, "disclosure": {"cobj":"0038", "value": "Other literature type"}, - "dissertation": {"cobj":"0006", "value": "Doctoral thesis"}, + "dissertation-thesis": {"cobj":"0006", "value": "Doctoral thesis"}, "edited-book": {"cobj":"0002", "value": "Book"}, "encyclopedia-entry": {"cobj":"0038", "value": "Other literature type"}, "lecture-speech": {"cobj":"0010", "value": "Lecture"}, @@ -37,5 +27,17 @@ "supervised-student-publication": {"cobj":"0001", "value": "Article"}, "technical-standard": {"cobj":"0038", "value": "Other literature type"}, "website": {"cobj":"0020", "value": "Other ORP type"}, - "working-paper": {"cobj":"0014", "value": "Research"} + "working-paper": {"cobj":"0014", "value": "Research"}, + "annotation": {"cobj":"0018", "value": "Annotation"}, + "physical-object": {"cobj":"0028", "value": "PhysicalObject"}, + "preprint": {"cobj":"0016", "value": "Preprint"}, + "software": {"cobj":"0029", "value": "Software"}, + "journal-issue": {"cobj":"0001", "value": "Article"}, + "translation": {"cobj":"0038", "value": "Other literature type"}, + "artistic-performance": {"cobj":"0020", "value": "Other ORP type"}, + "online-resource": {"cobj":"0020", "value": "Other ORP type"}, + "registered-copyright": {"cobj":"0020", "value": "Other ORP type"}, + "trademark": {"cobj":"0020", "value": "Other ORP type"}, + "invention": {"cobj":"0020", "value": "Other ORP type"}, + "spin-off-company": {"cobj":"0020", "value": "Other ORP type"} } \ No newline at end of file diff --git a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcidnodoi/oozie_app/workflow.xml b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcidnodoi/oozie_app/workflow.xml index 6cec48a6d..6513ff7e1 100644 --- a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcidnodoi/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/orcidnodoi/oozie_app/workflow.xml @@ -1,17 +1,18 @@ + + spark2GenNoDoiDatasetMaxExecutors + 40 + sparkDriverMemory memory for driver process - sparkExecutorMemory + spark2GenNoDoiDatasetExecutorMemory + 2G memory for individual executor - - sparkExecutorCores - number of cores used by single executor - oozieActionShareLibForSpark2 oozie action sharelib for spark 2.* @@ -73,8 +74,9 @@ eu.dnetlib.doiboost.orcidnodoi.SparkGenEnrichedOrcidWorks dhp-doiboost-${projectVersion}.jar - --executor-memory=${sparkExecutorMemory} - --executor-cores=${sparkExecutorCores} + --conf spark.dynamicAllocation.enabled=true + --conf spark.dynamicAllocation.maxExecutors=${spark2GenNoDoiDatasetMaxExecutors} + --executor-memory=${spark2GenNoDoiDatasetExecutorMemory} --driver-memory=${sparkDriverMemory} --conf spark.extraListeners=${spark2ExtraListeners} --conf spark.yarn.historyServer.address=${spark2YarnHistoryServerAddress} @@ -83,8 +85,7 @@ -w${workingPath}/ -n${nameNode} - -f- - -owno_doi_works/ + -ilast_orcid_dataset -oewno_doi_dataset diff --git a/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/orcid/OrcidClientTest.java b/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/orcid/OrcidClientTest.java index 66a7badb7..d96955c4a 100644 --- a/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/orcid/OrcidClientTest.java +++ b/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/orcid/OrcidClientTest.java @@ -10,30 +10,28 @@ import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.time.Duration; -import java.time.LocalDateTime; -import java.time.temporal.TemporalUnit; -import java.util.Arrays; -import java.util.Date; -import java.util.List; +import java.util.*; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; +import org.apache.commons.compress.utils.Lists; +import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; -import org.apache.spark.sql.catalyst.expressions.objects.AssertNotNull; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.mortbay.log.Log; import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.orcid.AuthorData; +import eu.dnetlib.doiboost.orcid.xml.XMLRecordParserTest; import jdk.nashorn.internal.ir.annotations.Ignore; public class OrcidClientTest { - final String orcidId = "0000-0001-7291-3210"; final int REQ_LIMIT = 24; final int REQ_MAX_TEST = 100; final int RECORD_DOWNLOADED_COUNTER_LOG_INTERVAL = 10; @@ -42,69 +40,45 @@ public class OrcidClientTest { String toNotRetrieveDate = "2019-09-29 23:59:59.000000"; String lastUpdate = "2019-09-30 00:00:00"; String shortDate = "2020-05-06 16:06:11"; + final String REQUEST_TYPE_RECORD = "record"; + final String REQUEST_TYPE_WORK = "work/47652866"; + final String REQUEST_TYPE_WORKS = "works"; + + private static Path testPath; + + @BeforeAll + private static void setUp() throws IOException { + testPath = Files.createTempDirectory(XMLRecordParserTest.class.getName()); + System.out.println("using test path: " + testPath); + } // curl -i -H "Accept: application/vnd.orcid+xml" // -H 'Authorization: Bearer 78fdb232-7105-4086-8570-e153f4198e3d' // 'https://api.orcid.org/v3.0/0000-0001-7291-3210/record' @Test - private void multipleDownloadTest() throws Exception { - int toDownload = 10; - long start = System.currentTimeMillis(); - OrcidDownloader downloader = new OrcidDownloader(); - TarArchiveInputStream input = new TarArchiveInputStream( - new GzipCompressorInputStream(new FileInputStream("/tmp/last_modified.csv.tar"))); - TarArchiveEntry entry = input.getNextTarEntry(); - BufferedReader br = null; - StringBuilder sb = new StringBuilder(); - int rowNum = 0; - int entryNum = 0; - int modified = 0; - while (entry != null) { - br = new BufferedReader(new InputStreamReader(input)); // Read directly from tarInput - String line; - while ((line = br.readLine()) != null) { - String[] values = line.toString().split(","); - List recordInfo = Arrays.asList(values); - String orcidId = recordInfo.get(0); - if (downloader.isModified(orcidId, recordInfo.get(3))) { - slowedDownDownload(orcidId); - modified++; - } - rowNum++; - if (modified > toDownload) { - break; - } - } - entryNum++; - entry = input.getNextTarEntry(); - } - long end = System.currentTimeMillis(); - logToFile("start test: " + new Date(start).toString()); - logToFile("end test: " + new Date(end).toString()); - } - - @Test - private void downloadTest(String orcid) throws Exception { - String record = testDownloadRecord(orcid); - String filename = "/tmp/downloaded_".concat(orcid).concat(".xml"); + public void downloadTest() throws Exception { + final String orcid = "0000-0001-7291-3210"; + String record = testDownloadRecord(orcid, REQUEST_TYPE_RECORD); + String filename = testPath + "/downloaded_record_".concat(orcid).concat(".xml"); File f = new File(filename); OutputStream outStream = new FileOutputStream(f); IOUtils.write(record.getBytes(), outStream); } - private String testDownloadRecord(String orcidId) throws Exception { + private String testDownloadRecord(String orcidId, String dataType) throws Exception { try (CloseableHttpClient client = HttpClients.createDefault()) { - HttpGet httpGet = new HttpGet("https://api.orcid.org/v3.0/" + orcidId + "/record"); + HttpGet httpGet = new HttpGet("https://api.orcid.org/v3.0/" + orcidId + "/" + dataType); httpGet.addHeader("Accept", "application/vnd.orcid+xml"); httpGet.addHeader("Authorization", "Bearer 78fdb232-7105-4086-8570-e153f4198e3d"); - logToFile("start connection: " + new Date(System.currentTimeMillis()).toString()); + long start = System.currentTimeMillis(); CloseableHttpResponse response = client.execute(httpGet); - logToFile("end connection: " + new Date(System.currentTimeMillis()).toString()); + long end = System.currentTimeMillis(); if (response.getStatusLine().getStatusCode() != 200) { - System.out - .println("Downloading " + orcidId + " status code: " + response.getStatusLine().getStatusCode()); + logToFile( + testPath, "Downloading " + orcidId + " status code: " + response.getStatusLine().getStatusCode()); } + logToFile(testPath, orcidId + " " + dataType + " " + (end - start) / 1000 + " seconds"); return IOUtils.toString(response.getEntity().getContent()); } catch (Throwable e) { e.printStackTrace(); @@ -129,7 +103,7 @@ public class OrcidClientTest { } String[] values = line.split(","); List recordInfo = Arrays.asList(values); - testDownloadRecord(recordInfo.get(0)); + testDownloadRecord(recordInfo.get(0), REQUEST_TYPE_RECORD); long endReq = System.currentTimeMillis(); nReqTmp++; if (nReqTmp == REQ_LIMIT) { @@ -189,20 +163,24 @@ public class OrcidClientTest { final String base64CompressedRecord = IOUtils .toString(getClass().getResourceAsStream("0000-0003-3028-6161.compressed.base64")); final String recordFromSeqFile = ArgumentApplicationParser.decompressValue(base64CompressedRecord); - logToFile("\n\ndownloaded \n\n" + recordFromSeqFile); - final String downloadedRecord = testDownloadRecord("0000-0003-3028-6161"); + logToFile(testPath, "\n\ndownloaded \n\n" + recordFromSeqFile); + final String downloadedRecord = testDownloadRecord("0000-0003-3028-6161", REQUEST_TYPE_RECORD); assertTrue(recordFromSeqFile.equals(downloadedRecord)); } @Test - private void lambdaFileReaderTest() throws Exception { + @Disabled + public void lambdaFileReaderTest() throws Exception { + String last_update = "2021-01-12 00:00:06.685137"; TarArchiveInputStream input = new TarArchiveInputStream( - new GzipCompressorInputStream(new FileInputStream("/develop/last_modified.csv.tar"))); + new GzipCompressorInputStream(new FileInputStream("/tmp/last_modified.csv.tar"))); TarArchiveEntry entry = input.getNextTarEntry(); BufferedReader br = null; StringBuilder sb = new StringBuilder(); - int rowNum = 0; + int rowNum = 1; + int modifiedNum = 1; int entryNum = 0; + boolean firstNotModifiedFound = false; while (entry != null) { br = new BufferedReader(new InputStreamReader(input)); // Read directly from tarInput String line; @@ -210,59 +188,44 @@ public class OrcidClientTest { String[] values = line.toString().split(","); List recordInfo = Arrays.asList(values); assertTrue(recordInfo.size() == 4); - + String orcid = recordInfo.get(0); + String modifiedDate = recordInfo.get(3); rowNum++; - if (rowNum == 1) { + if (rowNum == 2) { assertTrue(recordInfo.get(3).equals("last_modified")); - } else if (rowNum == 2) { - assertTrue(recordInfo.get(0).equals("0000-0002-0499-7333")); + } else { +// SparkDownloadOrcidAuthors.lastUpdate = last_update; +// boolean isModified = SparkDownloadOrcidAuthors.isModified(orcid, modifiedDate); +// if (isModified) { +// modifiedNum++; +// } else { +// if (!firstNotModifiedFound) { +// firstNotModifiedFound = true; +// logToFile(orcid + " - " + modifiedDate + " > " + isModified); +// } +// } + } } entryNum++; assertTrue(entryNum == 1); entry = input.getNextTarEntry(); + } + logToFile(testPath, "modifiedNum : " + modifiedNum + " / " + rowNum); } - @Test - private void lambdaFileCounterTest() throws Exception { - final String lastUpdate = "2020-09-29 00:00:00"; - OrcidDownloader downloader = new OrcidDownloader(); - TarArchiveInputStream input = new TarArchiveInputStream( - new GzipCompressorInputStream(new FileInputStream("/tmp/last_modified.csv.tar"))); - TarArchiveEntry entry = input.getNextTarEntry(); - BufferedReader br = null; - StringBuilder sb = new StringBuilder(); - int rowNum = 0; - int entryNum = 0; - int modified = 0; - while (entry != null) { - br = new BufferedReader(new InputStreamReader(input)); // Read directly from tarInput - String line; - while ((line = br.readLine()) != null) { - String[] values = line.toString().split(","); - List recordInfo = Arrays.asList(values); - String orcidId = recordInfo.get(0); - if (downloader.isModified(orcidId, recordInfo.get(3))) { - modified++; - } - rowNum++; - } - entryNum++; - entry = input.getNextTarEntry(); - } - logToFile("rowNum: " + rowNum); - logToFile("modified: " + modified); - } - - private void logToFile(String log) - throws IOException { + public static void logToFile(Path basePath, String log) throws IOException { log = log.concat("\n"); - Path path = Paths.get("/tmp/orcid_log.txt"); + Path path = basePath.resolve("orcid_log.txt"); + if (!Files.exists(path)) { + Files.createFile(path); + } Files.write(path, log.getBytes(), StandardOpenOption.APPEND); } @Test + @Disabled private void slowedDownDownloadTest() throws Exception { String orcid = "0000-0001-5496-1243"; String record = slowedDownDownload(orcid); @@ -281,16 +244,17 @@ public class OrcidClientTest { CloseableHttpResponse response = client.execute(httpGet); long endReq = System.currentTimeMillis(); long reqSessionDuration = endReq - start; - logToFile("req time (millisec): " + reqSessionDuration); + logToFile(testPath, "req time (millisec): " + reqSessionDuration); if (reqSessionDuration < 1000) { - logToFile("wait ...."); + logToFile(testPath, "wait ...."); Thread.sleep(1000 - reqSessionDuration); } long end = System.currentTimeMillis(); long total = end - start; - logToFile("total time (millisec): " + total); + logToFile(testPath, "total time (millisec): " + total); if (response.getStatusLine().getStatusCode() != 200) { - logToFile("Downloading " + orcidId + " status code: " + response.getStatusLine().getStatusCode()); + logToFile( + testPath, "Downloading " + orcidId + " status code: " + response.getStatusLine().getStatusCode()); } return IOUtils.toString(response.getEntity().getContent()); } catch (Throwable e) { @@ -298,4 +262,89 @@ public class OrcidClientTest { } return new String(""); } + + @Test + public void downloadWorkTest() throws Exception { + String orcid = "0000-0003-0015-1952"; + String record = testDownloadRecord(orcid, REQUEST_TYPE_WORK); + String filename = "/tmp/downloaded_work_".concat(orcid).concat(".xml"); + File f = new File(filename); + OutputStream outStream = new FileOutputStream(f); + IOUtils.write(record.getBytes(), outStream); + } + + @Test + public void downloadRecordTest() throws Exception { + String orcid = "0000-0001-5004-5918"; + String record = testDownloadRecord(orcid, REQUEST_TYPE_RECORD); + String filename = "/tmp/downloaded_record_".concat(orcid).concat(".xml"); + File f = new File(filename); + OutputStream outStream = new FileOutputStream(f); + IOUtils.write(record.getBytes(), outStream); + } + + @Test + public void downloadWorksTest() throws Exception { + String orcid = "0000-0001-5004-5918"; + String record = testDownloadRecord(orcid, REQUEST_TYPE_WORKS); + String filename = "/tmp/downloaded_works_".concat(orcid).concat(".xml"); + File f = new File(filename); + OutputStream outStream = new FileOutputStream(f); + IOUtils.write(record.getBytes(), outStream); + } + + @Test + public void downloadSingleWorkTest() throws Exception { + String orcid = "0000-0001-5004-5918"; + String record = testDownloadRecord(orcid, REQUEST_TYPE_WORK); + String filename = "/tmp/downloaded_work_47652866_".concat(orcid).concat(".xml"); + File f = new File(filename); + OutputStream outStream = new FileOutputStream(f); + IOUtils.write(record.getBytes(), outStream); + } + + @Test + public void cleanAuthorListTest() throws Exception { + AuthorData a1 = new AuthorData(); + a1.setOid("1"); + a1.setName("n1"); + a1.setSurname("s1"); + a1.setCreditName("c1"); + AuthorData a2 = new AuthorData(); + a2.setOid("1"); + a2.setName("n1"); + a2.setSurname("s1"); + a2.setCreditName("c1"); + AuthorData a3 = new AuthorData(); + a3.setOid("3"); + a3.setName("n3"); + a3.setSurname("s3"); + a3.setCreditName("c3"); + List list = Lists.newArrayList(); + list.add(a1); + list.add(a2); + list.add(a3); + + Set namesAlreadySeen = new HashSet<>(); + assertTrue(list.size() == 3); + list.removeIf(a -> !namesAlreadySeen.add(a.getOid())); + assertTrue(list.size() == 2); + } + + @Test + @Ignore + public void testUpdatedRecord() throws Exception { + final String base64CompressedRecord = IOUtils + .toString(getClass().getResourceAsStream("0000-0003-3028-6161.compressed.base64")); + final String record = ArgumentApplicationParser.decompressValue(base64CompressedRecord); + logToFile(testPath, "\n\nrecord updated \n\n" + record); + } + + @Test + @Ignore + private void testUpdatedWork() throws Exception { + final String base64CompressedWork = "H4sIAAAAAAAAAM1XS2/jNhC+51cQOuxJsiXZSR03Vmq0G6Bo013E6R56oyXaZiOJWpKy4y783zvUg5Ksh5uiCJogisX5Zjj85sHx3f1rFKI94YKyeGE4I9tAJPZZQOPtwvj9+cGaGUhIHAc4ZDFZGEcijHvv6u7A+MtcPVCSSgsUQObYzuzaccBEguVuYYxt+LHgbwKP6a11M3WnY6UzrpB7KuiahlQeF0aSrkPqGwhcisWcxpLwGIcLYydlMh+PD4fDiHGfBvDcjmMxLhGlBglSH8vsIH0qGlLqBFRIGvvDWjWQ1iMJJ2CKBANqGlNqMbkj3IpxRPq1KkypFZFoDRHa0aRfq8JoNjhnfIAJJS6xPouiIQJyeYmGQzE+cO5cXqITcItBlKyASExD0a93jiwtvJDjYXDDAqBPHoH2wMmVWGNf8xyyaEBiSTeUDHHWBpd2Nmmc10yfbgHQrHCyIRxKjQwRUoFKPRwEnIgBnQJQVdGeQgJaCRN0OMnPkaUFVbD9WkpaIndQJowf+8EFoIpTErJjBFQOBavElFpfUxwC9ZcqvQErdQXhe+oPFF8BaObupYzVsYEOARzSoZBWmKqaBMHcV0Wf8oG0beIqD+Gdkz0lhyE3NajUW6fhQFSV9Nw/MCBYyofYa0EN7wrBz13eP+Y+J6obWgE8Pdd2JpYD94P77Ezmjj13b0bu5PqPu3EXumEnxEJaEVxSUIHammsra+53z44zt2/m1/bItaeVtQ6dhs3c4XytvW75IYUchMKvEHVUyqmnWBFAS0VJrqSvQde6vp251ux2NtFuKcVOi+oK9YY0M0Cn6o4J6WkvtEK2XJ1vfPGAZxSoK8lb+SxJBbLQx1CohOLndjJUywQWUFmqEi3G6Zaqf/7buOyYJd5IYpfmf0XipfP18pDR9cQCeEuJQI/Lx36bFbVnpBeL2UwmqQw7ApAvf4GeGGQdEbENgolui/wdpjHaYCmPCIPPAmGBIsxfoLUhyRCB0SeCakEBJRKBtfJ+UBbI15TG4PaGBAhWthx8DmFYtHZQujv1CWbLLdzmmUKmHEOWCe1/zdu78bn/+YH+hCOqOzcXfFwuP6OVT/P710crwqGXFrpNaM2GT3MXarw01i15TIi3pmtJXgtbTVGf3h6HKfF+wBAnPyTfdCChudlm5gZaoG//F9pPZsGQcqqbyZN5hBau5OoIJ3PPwjTKDuG4s5MZp2rMzF5PZoK34IT6PIFOPrk+mTiVO5aJH2C+JJRjE/06eoRfpJxa4VgyYaLlaJUv/EhCfATMU/76gEOfmehL/qbJNNHjaFna+CQYB8wvo9PpPFJ5MOrJ1Ix7USBZqBl7KRNOx1d3jex7SG6zuijqCMWRusBsncjZSrM2u82UJmqzpGhvUJN2t6caIM9QQgO9c0t40UROnWsJd2Rbs+nsxpna9u30ttNkjechmzHjEST+X5CkkuNY0GzQkzyFseAf7lSZuLwdh1xSXKvvQJ4g4abTYgPV7uMt3rskohlJmMa82kQkshtyBEIYqQ+YB8X3oRHg7iFKi/bZP+Ao+T6BJhIT/vNPi8ffZs+flk+r2v0WNroZiyWn6xRmadHqTJXsjLJczElAZX6TnJdoWTM1SI2gfutv3rjeBt5t06rVvNuWup29246tlvluO+u2/G92bK9DXheL6uFd/Q3EaRDZqBIAAA=="; + final String work = ArgumentApplicationParser.decompressValue(base64CompressedWork); + logToFile(testPath, "\n\nwork updated \n\n" + work); + } } diff --git a/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/orcid/xml/XMLRecordParserTest.java b/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/orcid/xml/XMLRecordParserTest.java index b7be5e5cd..2fe00bd57 100644 --- a/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/orcid/xml/XMLRecordParserTest.java +++ b/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/orcid/xml/XMLRecordParserTest.java @@ -1,20 +1,44 @@ package eu.dnetlib.doiboost.orcid.xml; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import com.fasterxml.jackson.databind.ObjectMapper; + +import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.schema.orcid.AuthorData; +import eu.dnetlib.dhp.schema.orcid.AuthorSummary; +import eu.dnetlib.dhp.schema.orcid.Work; +import eu.dnetlib.dhp.schema.orcid.WorkDetail; +import eu.dnetlib.doiboost.orcid.OrcidClientTest; import eu.dnetlib.doiboost.orcid.model.WorkData; import eu.dnetlib.doiboost.orcidnodoi.json.JsonWriter; +import eu.dnetlib.doiboost.orcidnodoi.xml.XMLRecordParserNoDoi; public class XMLRecordParserTest { + private static final String NS_WORK = "work"; + private static final String NS_WORK_URL = "http://www.orcid.org/ns/work"; + private static final String NS_COMMON_URL = "http://www.orcid.org/ns/common"; + private static final String NS_COMMON = "common"; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private static Path testPath; + + @BeforeAll + private static void setUp() throws IOException { + testPath = Files.createTempDirectory(XMLRecordParserTest.class.getName()); + } @Test - private void testOrcidAuthorDataXMLParser() throws Exception { + public void testOrcidAuthorDataXMLParser() throws Exception { String xml = IOUtils.toString(this.getClass().getResourceAsStream("summary_0000-0001-6828-479X.xml")); @@ -26,10 +50,11 @@ public class XMLRecordParserTest { System.out.println("name: " + authorData.getName()); assertNotNull(authorData.getSurname()); System.out.println("surname: " + authorData.getSurname()); + OrcidClientTest.logToFile(testPath, OBJECT_MAPPER.writeValueAsString(authorData)); } @Test - private void testOrcidXMLErrorRecordParser() throws Exception { + public void testOrcidXMLErrorRecordParser() throws Exception { String xml = IOUtils.toString(this.getClass().getResourceAsStream("summary_error.xml")); @@ -42,7 +67,7 @@ public class XMLRecordParserTest { } @Test - private void testOrcidWorkDataXMLParser() throws Exception { + public void testOrcidWorkDataXMLParser() throws Exception { String xml = IOUtils .toString( @@ -54,8 +79,7 @@ public class XMLRecordParserTest { assertNotNull(workData); assertNotNull(workData.getOid()); System.out.println("oid: " + workData.getOid()); - assertNotNull(workData.getDoi()); - System.out.println("doi: " + workData.getDoi()); + assertNull(workData.getDoi()); } @Test @@ -64,9 +88,6 @@ public class XMLRecordParserTest { String xml = IOUtils .toString( this.getClass().getResourceAsStream("summary_0000-0001-5109-1000_othername.xml")); - - XMLRecordParser p = new XMLRecordParser(); - AuthorData authorData = XMLRecordParser.VTDParseAuthorData(xml.getBytes()); assertNotNull(authorData); assertNotNull(authorData.getOtherNames()); @@ -74,4 +95,43 @@ public class XMLRecordParserTest { String jsonData = JsonWriter.create(authorData); assertNotNull(jsonData); } + +// @Test +// private void testWorkIdLastModifiedDateXMLParser() throws Exception { +// String xml = IOUtils +// .toString( +// this.getClass().getResourceAsStream("record_0000-0001-5004-5918.xml")); +// Map workIdLastModifiedDate = XMLRecordParser.retrieveWorkIdLastModifiedDate(xml.getBytes()); +// workIdLastModifiedDate.forEach((k, v) -> { +// try { +// OrcidClientTest +// .logToFile( +// k + " " + v + " isModified after " + SparkDownloadOrcidWorks.lastUpdateValue + ": " +// + SparkDownloadOrcidWorks.isModified("0000-0001-5004-5918", v)); +// } catch (IOException e) { +// } +// }); +// } + + @Test + public void testAuthorSummaryXMLParser() throws Exception { + String xml = IOUtils + .toString( + this.getClass().getResourceAsStream("record_0000-0001-5004-5918.xml")); + AuthorSummary authorSummary = XMLRecordParser.VTDParseAuthorSummary(xml.getBytes()); + authorSummary.setBase64CompressData(ArgumentApplicationParser.compressArgument(xml)); + OrcidClientTest.logToFile(testPath, JsonWriter.create(authorSummary)); + } + + @Test + public void testWorkDataXMLParser() throws Exception { + String xml = IOUtils + .toString( + this.getClass().getResourceAsStream("activity_work_0000-0003-2760-1191.xml")); + WorkDetail workDetail = XMLRecordParserNoDoi.VTDParseWorkData(xml.getBytes()); + Work work = new Work(); + work.setWorkDetail(workDetail); + work.setBase64CompressData(ArgumentApplicationParser.compressArgument(xml)); + OrcidClientTest.logToFile(testPath, JsonWriter.create(work)); + } } diff --git a/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/orcidnodoi/xml/OrcidNoDoiTest.java b/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/orcidnodoi/xml/OrcidNoDoiTest.java index 1f77197ab..efe01522c 100644 --- a/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/orcidnodoi/xml/OrcidNoDoiTest.java +++ b/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/orcidnodoi/xml/OrcidNoDoiTest.java @@ -21,8 +21,8 @@ import com.ximpleware.XPathParseException; import eu.dnetlib.dhp.parser.utility.VtdException; import eu.dnetlib.dhp.schema.orcid.AuthorData; -import eu.dnetlib.doiboost.orcidnodoi.model.Contributor; -import eu.dnetlib.doiboost.orcidnodoi.model.WorkDataNoDoi; +import eu.dnetlib.dhp.schema.orcid.Contributor; +import eu.dnetlib.dhp.schema.orcid.WorkDetail; import eu.dnetlib.doiboost.orcidnodoi.similarity.AuthorMatcher; public class OrcidNoDoiTest { @@ -48,7 +48,7 @@ public class OrcidNoDoiTest { if (p == null) { logger.info("XMLRecordParserNoDoi null"); } - WorkDataNoDoi workData = null; + WorkDetail workData = null; try { workData = p.VTDParseWorkData(xml.getBytes()); } catch (Exception e) { @@ -105,7 +105,7 @@ public class OrcidNoDoiTest { if (p == null) { logger.info("XMLRecordParserNoDoi null"); } - WorkDataNoDoi workData = null; + WorkDetail workData = null; try { workData = p.VTDParseWorkData(xml.getBytes()); } catch (Exception e) { @@ -136,7 +136,7 @@ public class OrcidNoDoiTest { if (p == null) { logger.info("XMLRecordParserNoDoi null"); } - WorkDataNoDoi workData = null; + WorkDetail workData = null; try { workData = p.VTDParseWorkData(xml.getBytes()); } catch (Exception e) { @@ -179,7 +179,7 @@ public class OrcidNoDoiTest { if (p == null) { logger.info("XMLRecordParserNoDoi null"); } - WorkDataNoDoi workData = null; + WorkDetail workData = null; try { workData = p.VTDParseWorkData(xml.getBytes()); } catch (Exception e) { @@ -308,7 +308,7 @@ public class OrcidNoDoiTest { if (p == null) { logger.info("XMLRecordParserNoDoi null"); } - WorkDataNoDoi workData = null; + WorkDetail workData = null; try { workData = p.VTDParseWorkData(xml.getBytes()); } catch (Exception e) { diff --git a/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/0000-0002-6664-7451_work.compressed.base64 b/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/0000-0002-6664-7451_work.compressed.base64 new file mode 100644 index 000000000..7e5a73b73 --- /dev/null +++ b/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/0000-0002-6664-7451_work.compressed.base64 @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/0000-0003-3028-6161.compressed.base64 b/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/0000-0003-3028-6161.compressed.base64 index 8dc3d32ad..34de6ba16 100644 --- a/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/0000-0003-3028-6161.compressed.base64 +++ b/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/0000-0003-3028-6161.compressed.base64 @@ -1 +1 @@ -H4sIAAAAAAAAAO1dW5fbthF+z6/A2XPal5biRaREqmvlrG+JG6/t4900bd+4JLSCQxIqSa1X+fUFeIUkAiIlkpFs5jRxTQ1mgAEw881gQF7/+Ox74AmGEcLBiyt1pFwBGDjYRcHji6tf799K5hWIYjtwbQ8H8MXVBkZXP85/uA6hg0N3lv4BVna8fHElK+Qfifw7lsaKZkoTdaJeASIgiGYoiGEY2N6Lq2Ucr2ay/PXr1xEOHeSS/z7KQSTnFHkL6K4dO066xWtSkORtXBTFKHDErRiioh1ckZHYMXQFzQqavBWOlzCUAtuH/FYlTd7Kh/4D0fcSrfitSppCG2GIQ4Em6M85rYN9X6SA9PecOp1CPnX6e069It3CZJYkF8Y28iJ+u13KnMPvcPNVKDAjKEbuk9aCkdOfC9rndA1JyIVBjBYIinS2T5zzWayDdAfw2mYEhVZCuIAh2ThQpJCSKG9nu24II0GbjKDcRU+ILEBphSMkXuS7lDkHotnf+a3orznlkmwTHG74xBlBOU8rD298okrRZOU0eav/rW2PqP7QTt8iy9tGMHxCjmDzZQTba/fQii3mhlgIokMkmtKSptxNEbRDh276dShYttt0ZQ/J30P4hOBXUTcLorzdw9oTzCr9dbd/hEGE16FIe3ukV/MfAPnnOrUfs4SY2TzpryzFOkRzyj0i7EvWFV7iWmZa7LGh3mUuapUQ7DVb4iieF2IL4uRxOhBZOJJrZsOyO5yRxFJ42LE9OIfBtVzxOBMoZHmd7ah86zGC8l+cECZbQPJhvMTu/DZxFFLCKYTutcwj3GcVrR98FFG/L7nEq801RdUlxZK08b2mzDR9NlZHlmX9t+S522JP454dxZJPwANRoptz1RRJVSV1eq+NZwZhrIx0TflvofuKNhXD9mzkQ3ceh2vIjDF7uk9PAE3KL/EOO812fhS0XoXIt8ONmMs2UTbPlTN5nRqYzA4JQFNuiWpqWDUlZSqpk3vVnCnaTLNGxsSqqeGsSxSggCcUoQfkoZgY/dX6wUPOVdbJKmBXMmE7mKw7pmsTSdEl1Ugm35ypxshUpmXXtqgr+VUPWMxVNGBm0CU0mT2iJxgkKC2avwwJ2sV0F4uoDjBc2D7yNgnt/PWacIwr+LFE5YzIzJQwj0sgyeDOSLSIGLIrmeG07Xp2PJaQ4w7pFtdk+adgTcgjxWtsywzj5GBIPKgcELEMMsCYI0th+5xmu+/7SLAKSorHVUHP2SNtb+ImYwCrdSyR+I74fVUxjYkyuRLs+9ojlQtmJLpaefZGQoELn4nl2NGByFaINcC3FV3rluWfIqH93/dpJMdDRD9ES9XUbItqoJQyKOZAkwzL1CTTMsfVeInHfQs/VXHZxk88Ngfx1F5DuZFCdtSX2L87B6/WEZDAGy+iiDfc5bltJavY2cSkhAkUwiF6RPQP5/g5qQ1ea03GYTDb/mQ00QdXh4naM08JcgcnJN7fUfKBLZULZ+yNFG9WxaK4WRNkG4J3rwtOe5S1eD7Z3hrO9SmZBFXVp4pSyS+lqsWQ+MY5E1RFSXdHhJBE5V/t0JXtpOevUxgwIuQ/pk/evX7BdOOvtr/6x8oO4wDSX24/mPcfbz7fVfaOiqzVtxB6SVxAc0vzCHqLSnZbVNt+psr8VzkaFtHU9a9FlMTi5OxhGWozkbkUrX0KvoWIoYzRj49Y1Jrwku0mk2cUIgeWbhsYlbyKTKcYgxRUTZAHO1zdmmnaSB2bDZAHOzOLBcERaeD5GOL1qqGjPrErnEUfyRVkha5K3ZarqcBI+tTSLGMP1ahigJQzlPPmFQhLbHB3oREbmVsUwChjvS406kPrrAwRRNqnO+SO2RYtu2SW9YlumWXV2DUnjeVGWqnCShx3fBgoHXLErEAXUo9EM7gpx1dL6BP7FW4KLrsUQnYh9qAUo9iD80/L0pEzj8VLLSaiBEuSpd2Q0JVupXJKkycH25F/6dIwi2bpg4PtXHsz14xSLfbmkPoPDKawbIFoF1YN2TxqyKp2zJDVJkMWD6VMND/aAfojMamHO5Esul8DlBxqxhuAF+C3DfZRUG5F/rpkGWWphnqb3iGi5u/t0PYRLO0yfVireQgf6eB++0+5BdIn9YTjdUC24PzXEhzmjw4bnIPDLGYXRbb/gB7Xia+pNyn12rOwUdfVSbmCajVpKj1x9amt+/zuw08/fXz/ukoiQ3ZYi02Vw5w9iEivZQFO2UXm9YFYm5htC5uY5H8j3TD+dMymVWSiGmA2rWXMtq+XEzFbC1pnZQyYbcBsR2C2l7azhB4OI+pl7xxEDzMBCsC+hA4RnaL3ieiUc0B0ynGITjGOQnTm+SG6e/hsR8COwc2aJk86R3Y7YhoDu/t/f/fATtXGY2VAdiVZ68hui67MBXIyikUp1oHj2oLs2JxiwxTqbkfbzCke0RWW/0F8WiiLUW8FQlXHmmFYk8Z5xYKnXHI4FaYeUE+LqcUjlc/KGGDqdwRTPy03EXKIlw9ccEPAJA6w30KakejKxU6MQ9sDn7OCFsI/wg4xMrAPwKpax6E3/Rj0pqgdANaT8dvH3z17iX27c+D2AYe+fQJw+/jLmQG3+vBC5IzaABglMqhGGFmNvBBe5DS8c8/dMnYhsz1iHtfyeoWQH0PG48TUYgtZsXQ8Xls17kJu25Q8fnv127Vq+0pqHt+sql7ILafh8aAXDYQMEoJG9XMWrQlVjHuFwoyZYY0svUn9HNO5o7Kgp4ln+bMo80DeoKQ8bDv3imNcjBpUzojYpbUxqjJSVd2StZGiqKqlGF9g1KSURiQhoGbcQ39AF8QEgkQouWZCK7Kv6sstmTQTntfzUGxIlJYgw9pCKytzRNJOrdApeFc0q/ITtVbdNd2Pya6tCGeMiaqa1tgSBi+0rVxSVtWc1igdZZW2m28X78BT8+2n729WRsNApkYpKduqVjmpKlmmqUuqpdYsJ2UlbMU0VZx6jmka1NqyQcurEEdRCBdtRzTJbhEED2x/UrI77NkheLW0w0di4z5DFz+Dtx7+Cl7aMdmYmxm4AfckEoI++LTEMYYedEhI5NBEu0MimZvVKsS2syz6Jwpd5EP9q++KONT1pr4ll8Rj2a5b4knpzTXxOtCde+JJbMtFJfxruSke5QGA1Fg1XHVkW5quwy9k51PpJFRGjgfz3cRdooXNTNxcGli1ny8oLW8tMel48qGkpuCf6d+S85UlBG92TMwddhCMN9l4t5tW4Io9xCCIOQ+UKBwFyLfvfOiTkTYdnzcgh8htFZBrUkQWuWQaim7qk4nFrKMO4XhNqceC8X7hcXuT20m0pVjyp3/dvRqR5T4dmePJZKKbvYVcRPjqKXIOC7+MqW4jEiLrnRgefXw4EiopO4iExKbv1EjodMPKyjiDSKjxxTpWwomX61hWf2YkdMJFO5Zl08t2bNvv4Qju2MuIXVzA2+pYy+FsEqM+YS+2kQNCGK2IfYYUWAZ2TDrreRuAHWcdhih4BBEFmDhw1wTx0b+uvQVyIfBRAAlpCc6HkLYL388T1a//5/Xizwk1Ob05cgkch+t5LNvE9jwZPeF7nviLyy9wb/qmL09LrviSVfBiX3eHLvnWSUgQ25m9SEha2SvqujvPSNSpP20hI0FNC3j35s0boOvxEmx5lbsVdBBZYVEMXhUK+DugNhHQdpebpzD1885TdBHKKupE/jKKsOfbMfUoxkixRophHGPVjvFmjeRfRkDb3oR3lZjSLd0YK5apTPtMTB2QehmT22K2wqidraguN20/W2G2d7e9BfPKyhiyFUO2YshWNNbrhWcrwC8wIlYWxRA44SaKh6REDyiOJ613JMfryJCa6BLd8WT0hPB44r/51ESpuzZSE/3XSpQvj+g0M5HWa70hqnncgFubegfiFZK7KlkpF/Sol7jUHMSk5iuad/lfcg6CTalPRtOpQZxHL5HpbjpdLPwyAtT2prq7shhlbFhT09T7mOOaUi9jclvMPmi1sw/V72RpP/vAmr72sw9NDSsrY8g+DNmHIfvQWK8Xnn0YaiXarJU4CdjxRPUL7ni9GBISXQI+noyeQB9P/DefkCh1d6G1Ej1lJMoiCPA5/SojcSHxEqb1E5zSiYipnbjcZMW0hXeEX2IEq1uqppCtofZSJlFT6mVEsO1NbtuZqMxGus8j9sba7qmGTkDFSNG0Pia+sfxTLtpXMvwOLtGrE0U3FKvG1ZGSsoN0iJFcc58mxnU607XRpP43mFi+1abboKZb05OX1hozXR0Z4/7SIUN4nTSWG2mlKv/R7dvAmgfQb+2HMMNMFPO8cu8hiZ2JEcIusMPQ3kQAP8EQePRyPXkCbYKLCPJ4XNK9RncssXrZhXoX5m8PAgscArhYIPpa2xhEyakOTA98CIrMvpk9BNz9+y+e/N59GK8jx/kxHrfLjfCS90G7iAR2cRLoZU/k7DRYXiEk3ymWNlU03VR1RZko2rhRONf/+bLRTzRX73wZtBSzlb0QH9botQ9r9L4utpqdXmzt7zNIw2HNbuPhsOYw67NBk9/LYc2ANc8Ja353yHI44ukyI8qT0VNWlCf+cgOA2jWnue4us+b0rGKCSznG2csFTvXJeR/j2OEzeuoi1083y5cHJ4XwOIgphtfMqazJ6niijhZrzxut3MXflrHvtZ/wl2AYEvixZ9nq2SnaNgkS56bCvGueed6Ajw+jyH6E81d2EOAYFH0E5TdhQDJWkMzF7CT9bUlsZDD3lPd9HHNwenEGZ2YJBpvqMl0EtyrBXvQGg97L6+5Y0f4B0cO52NHnYmbtczGz83MxazZWR4bZ3ldyWvCFrIzhXGw4F6Nkb+zQ24Abj+y97JsSeAFIJ+kHGglSXDNo8mfsQ6IeO0IR/X7jz+uAlpfGdDAognYEwS3ZjUN9aaeuhyeoT/fD68O3cKTF6c2Ri+nYaIDHtNeIgNeJE6OCLbYnRgb7vM4wOkg6eVqEwJuKb2HHnZ5DEkyc/RCR1enEDNY4x+RQmQTu6+XtLxH28GPyznbmQ8yXmhWyWvhY56XFsuOpKmdTNVrhANJPFZjj8eQYU38Momgkf4hqj4xqVUX8vdcyqk0ou41qVbLh9JFptvii8NP3MitjiGqHqJaSJfEsMVyv8cqm1x7T8w8YYhKtktD1Fsb2A/ZQ5NNA9pY8pef19BLlTbTxVzH2iRFwwOeJrIFb5JQffB0i2o4cEE9a706I15EBae8U6xLN/0V7K9T9eWJttQbWZtv56YeVy5o2n/9h5RYw+qf3+A58/PDmYsG4qZ35N027PaL1wq1Y1NBkS1anujac0NbKwTRV33BAKxzkpQW1qqXLdA3cKupUnfb3napMrC8QO4SwR4ewNa4ElJQdh7DqTJuOpnqLVwJO94CsjCGEHULYpJAPPpNlv8jeu5Acy5LpCOj+osHs78gN4AY8evgBxyGycbTxHBiSaNdNo11vE2Gp+mcS89IS9Q3wh9i2Oz/EE9KXL+LJ/xYiWU5vzvaUtruggNeHb/aQtpsAIenjcEbb4Rktd94u5Ii2Ttqo3SPa92iFXPAZRkSes+whH7T1G2WRTfHW8/L/lgKus0sbs/SP+Q//BxvQAv4zvAAA \ No newline at end of file +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 \ No newline at end of file diff --git a/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/xml/record_0000-0001-5004-5918.xml b/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/xml/record_0000-0001-5004-5918.xml new file mode 100644 index 000000000..9534686ae --- /dev/null +++ b/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/xml/record_0000-0001-5004-5918.xml @@ -0,0 +1,1202 @@ + + + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + + en + + + Direct + 2016-11-06T20:12:32.296Z + 2020-04-23T07:30:59.917Z + true + false + false + + + 2017-01-04T07:46:27.991Z + + 2016-11-06T20:12:32.525Z + 2016-11-06T20:12:32.525Z + Aurélie + Prémaud + + + + + + + + 2017-01-04T07:46:27.991Z + + 2017-01-04T07:46:27.991Z + 2017-01-04T07:46:27.991Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + ResearcherID + A-2095-2017 + http://www.researcherid.com/rid/A-2095-2017 + self + + + + + 2019-04-08T23:37:26.263Z + + + + + + + + + + + + 2019-04-08T23:37:26.263Z + + 2019-04-08T23:37:26.263Z + + + doi + 10.1155/2019/7245142 + 10.1155/2019/7245142 + https://doi.org/10.1155/2019/7245142 + self + + + + 2019-04-08T23:37:26.263Z + 2019-04-08T23:37:26.263Z + + + https://orcid.org/client/0000-0001-9884-1913 + 0000-0001-9884-1913 + orcid.org + + Crossref + + + A Prognostic Tool for Individualized Prediction of Graft Failure Risk within Ten Years after Kidney Transplantation + + + + doi + 10.1155/2019/7245142 + 10.1155/2019/7245142 + https://doi.org/10.1155/2019/7245142 + self + + + https://doi.org/10.1155/2019/7245142 + journal-article + + 2019 + 04 + 08 + + Journal of Transplantation + + + + 2018-10-03T15:11:13.783Z + + + doi + 10.1371/journal.pone.0180236 + 10.1371/journal.pone.0180236 + https://doi.org/10.1371/journal.pone.0180236 + self + + + + 2020-11-30T01:02:03.444Z + 2020-11-30T01:02:03.444Z + + + https://orcid.org/client/0000-0001-9884-1913 + 0000-0001-9884-1913 + orcid.org + + Crossref + + + An adjustable predictive score of graft survival in kidney transplant patients and the levels of risk linked to de novo donor-specific anti-HLA antibodies + + + + doi + 10.1371/journal.pone.0180236 + 10.1371/journal.pone.0180236 + https://doi.org/10.1371/journal.pone.0180236 + self + + + https://doi.org/10.1371/journal.pone.0180236 + journal-article + + 2017 + 07 + 03 + + PLOS ONE + + + + 2018-08-23T12:01:11.624Z + + + doi + 10.1038/clpt.2014.140 + 10.1038/clpt.2014.140 + self + + + wosuid + WOS:000342675400030 + wos:000342675400030 + self + + + + 2018-08-23T12:01:11.624Z + 2018-08-23T12:01:11.624Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + Exposure to mycophenolic acid better predicts immunosuppressive efficacy than exposure to calcineurin inhibitors in renal transplant patients + + + + doi + 10.1038/clpt.2014.140 + 10.1038/clpt.2014.140 + self + + + wosuid + WOS:000342675400030 + wos:000342675400030 + self + + + http://gateway.webofknowledge.com/gateway/Gateway.cgi?GWVersion=2&SrcAuth=ORCID&SrcApp=OrcidOrg&DestLinkType=FullRecord&DestApp=WOS_CPL&KeyUT=WOS:000342675400030&KeyUID=WOS:000342675400030 + journal-article + + 2014 + + Clinical Pharmacology and Therapeutics + + + + 2018-08-23T12:01:11.635Z + + + wosuid + WOS:000336395700020 + wos:000336395700020 + self + + + doi + 10.1007/s00280-014-2466-0 + 10.1007/s00280-014-2466-0 + self + + + + 2018-08-23T12:01:11.635Z + 2018-08-23T12:01:11.635Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + Pharmacokinetics and exposure-effect relationships of capecitabine in elderly patients with breast or colorectal cancer + + + + doi + 10.1007/s00280-014-2466-0 + 10.1007/s00280-014-2466-0 + self + + + wosuid + WOS:000336395700020 + wos:000336395700020 + self + + + http://gateway.webofknowledge.com/gateway/Gateway.cgi?GWVersion=2&SrcAuth=ORCID&SrcApp=OrcidOrg&DestLinkType=FullRecord&DestApp=WOS_CPL&KeyUT=WOS:000336395700020&KeyUID=WOS:000336395700020 + journal-article + + 2014 + + Cancer Chemotherapy and Pharmacology + + + + 2018-08-23T12:01:11.639Z + + + doi + 10.1007/s40262-013-0037-x + 10.1007/s40262-013-0037-x + self + + + wosuid + WOS:000318524800005 + wos:000318524800005 + self + + + + 2018-08-23T12:01:11.639Z + 2018-08-23T12:01:11.639Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + Ciclosporin population pharmacokinetics and Bayesian estimation in thoracic transplant recipients + + + + doi + 10.1007/s40262-013-0037-x + 10.1007/s40262-013-0037-x + self + + + wosuid + WOS:000318524800005 + wos:000318524800005 + self + + + http://gateway.webofknowledge.com/gateway/Gateway.cgi?GWVersion=2&SrcAuth=ORCID&SrcApp=OrcidOrg&DestLinkType=FullRecord&DestApp=WOS_CPL&KeyUT=WOS:000318524800005&KeyUID=WOS:000318524800005 + journal-article + + 2013 + + Clinical Pharmacokinetics + + + + 2018-08-23T12:01:11.643Z + + + doi + 10.1016/j.phrs.2013.03.009 + 10.1016/j.phrs.2013.03.009 + self + + + wosuid + WOS:000319645300006 + wos:000319645300006 + self + + + + 2018-08-23T12:01:11.643Z + 2018-08-23T12:01:11.643Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + Impact of longitudinal exposure to mycophenolic acid on acute rejection in renal-transplant recipients using a joint modeling approach + + + + doi + 10.1016/j.phrs.2013.03.009 + 10.1016/j.phrs.2013.03.009 + self + + + wosuid + WOS:000319645300006 + wos:000319645300006 + self + + + http://gateway.webofknowledge.com/gateway/Gateway.cgi?GWVersion=2&SrcAuth=ORCID&SrcApp=OrcidOrg&DestLinkType=FullRecord&DestApp=WOS_CPL&KeyUT=WOS:000319645300006&KeyUID=WOS:000319645300006 + journal-article + + 2013 + + Pharmacological Research + + + + 2018-08-23T12:01:11.646Z + + + doi + 10.2165/11594050-000000000-00000 + 10.2165/11594050-000000000-00000 + self + + + + 2018-08-23T12:01:11.646Z + 2018-08-23T12:01:11.646Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + Bayesian estimation of mycophenolate mofetil in lung transplantation, using a population pharmacokinetic model developed in kidney and lung transplant recipients + + + + doi + 10.2165/11594050-000000000-00000 + 10.2165/11594050-000000000-00000 + self + + + http://www.ncbi.nlm.nih.gov/pubmed/22054177 + journal-article + + 2012 + + Clinical Pharmacokinetics + + + + 2018-08-23T12:01:11.650Z + + + doi + 10.1016/j.phrs.2011.01.005 + 10.1016/j.phrs.2011.01.005 + self + + + wosuid + WOS:000290892300011 + wos:000290892300011 + self + + + + 2018-08-23T12:01:11.650Z + 2018-08-23T12:01:11.650Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + Inhibition of T-cell activation and proliferation by mycophenolic acid in patients awaiting liver transplantation: PK/PD relationships + + + + doi + 10.1016/j.phrs.2011.01.005 + 10.1016/j.phrs.2011.01.005 + self + + + wosuid + WOS:000290892300011 + wos:000290892300011 + self + + + http://gateway.webofknowledge.com/gateway/Gateway.cgi?GWVersion=2&SrcAuth=ORCID&SrcApp=OrcidOrg&DestLinkType=FullRecord&DestApp=WOS_CPL&KeyUT=WOS:000290892300011&KeyUID=WOS:000290892300011 + journal-article + + 2011 + + Pharmacological Research + + + + 2018-08-23T12:01:11.653Z + + + wosuid + WOS:000290557800004 + wos:000290557800004 + self + + + doi + 10.1097/FTD.0b013e31821633a6 + 10.1097/ftd.0b013e31821633a6 + self + + + + 2018-08-23T12:01:11.653Z + 2018-08-23T12:01:11.653Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + Large scale analysis of routine dose adjustments of mycophenolate mofetil based on global exposure in renal transplant patients + + + + doi + 10.1097/FTD.0b013e31821633a6 + 10.1097/ftd.0b013e31821633a6 + self + + + wosuid + WOS:000290557800004 + wos:000290557800004 + self + + + http://gateway.webofknowledge.com/gateway/Gateway.cgi?GWVersion=2&SrcAuth=ORCID&SrcApp=OrcidOrg&DestLinkType=FullRecord&DestApp=WOS_CPL&KeyUT=WOS:000290557800004&KeyUID=WOS:000290557800004 + journal-article + + 2011 + + Therapeutic Drug Monitoring + + + + 2018-08-23T12:01:11.656Z + + + wosuid + WOS:000288041400008 + wos:000288041400008 + self + + + doi + 10.1016/j.phrs.2010.10.017 + 10.1016/j.phrs.2010.10.017 + self + + + + 2018-08-23T12:01:11.656Z + 2018-08-23T12:01:11.656Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + Population pharmacokinetics of mycophenolic acid in pediatric renal transplant patients using parametric and nonparametric approaches + + + + doi + 10.1016/j.phrs.2010.10.017 + 10.1016/j.phrs.2010.10.017 + self + + + wosuid + WOS:000288041400008 + wos:000288041400008 + self + + + http://gateway.webofknowledge.com/gateway/Gateway.cgi?GWVersion=2&SrcAuth=ORCID&SrcApp=OrcidOrg&DestLinkType=FullRecord&DestApp=WOS_CPL&KeyUT=WOS:000288041400008&KeyUID=WOS:000288041400008 + journal-article + + 2011 + + Pharmacological Research + + + + 2018-08-23T12:01:11.660Z + + + wosuid + WOS:000275009700011 + wos:000275009700011 + self + + + doi + 10.1016/j.phrs.2009.09.006 + 10.1016/j.phrs.2009.09.006 + self + + + + 2018-08-23T12:01:11.660Z + 2018-08-23T12:01:11.660Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + Feasibility of, and critical paths for mycophenolate mofetil Bayesian dose adjustment: pharmacological re-appraisal of a concentration-controlled versus fixed-dose trial in renal transplant recipients + + + + doi + 10.1016/j.phrs.2009.09.006 + 10.1016/j.phrs.2009.09.006 + self + + + wosuid + WOS:000275009700011 + wos:000275009700011 + self + + + http://gateway.webofknowledge.com/gateway/Gateway.cgi?GWVersion=2&SrcAuth=ORCID&SrcApp=OrcidOrg&DestLinkType=FullRecord&DestApp=WOS_CPL&KeyUT=WOS:000275009700011&KeyUID=WOS:000275009700011 + journal-article + + 2010 + + Pharmacological Research + + + + 2018-08-23T12:01:11.664Z + + + doi + 10.2165/11535950-000000000-00000 + 10.2165/11535950-000000000-00000 + self + + + + 2018-08-23T12:01:11.664Z + 2018-08-23T12:01:11.664Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + Population pharmacokinetics and Bayesian estimation of tacrolimus exposure in renal transplant recipients on a new once-daily formulation + + + + doi + 10.2165/11535950-000000000-00000 + 10.2165/11535950-000000000-00000 + self + + + http://www.ncbi.nlm.nih.gov/pubmed/20818834 + journal-article + + 2010 + + Clinical Pharmacokinetics + + + + 2018-08-23T12:01:11.671Z + + + doi + 10.1097/FTD.0b013e3181a8f0ae + 10.1097/ftd.0b013e3181a8f0ae + self + + + + 2018-08-23T12:01:11.671Z + 2018-08-23T12:01:11.671Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + Performance of the new mycophenolate assay based on IMPDH enzymatic activity for pharmacokinetic investigations and setup of Bayesian estimators in different populations of allograft recipients + + + + doi + 10.1097/FTD.0b013e3181a8f0ae + 10.1097/ftd.0b013e3181a8f0ae + self + + + http://www.ncbi.nlm.nih.gov/pubmed/19571778 + journal-article + + 2009 + + Therapeutic Drug Monitoring + + + + 2018-08-23T12:01:11.675Z + + + doi + 10.2165/11318080-000000000-00000 + 10.2165/11318080-000000000-00000 + self + + + + 2018-08-23T12:01:11.675Z + 2018-08-23T12:01:11.675Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + Tacrolimus population pharmacokinetic-pharmacogenetic analysis and Bayesian estimation in renal transplant recipients + + + + doi + 10.2165/11318080-000000000-00000 + 10.2165/11318080-000000000-00000 + self + + + http://www.ncbi.nlm.nih.gov/pubmed/19902988 + journal-article + + 2009 + + Clinical Pharmacokinetics + + + + 2018-08-23T12:01:11.678Z + + + doi + 10.1111/j.1365-2125.2006.02509.x + 10.1111/j.1365-2125.2006.02509.x + self + + + wosuid + WOS:000240556900012 + wos:000240556900012 + self + + + + 2018-08-23T12:01:11.678Z + 2018-08-23T12:01:11.678Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + A comparison of the effect of ciclosporin and sirolimus on the pharmokinetics of mycophenolate in renal transplant patients + + + + doi + 10.1111/j.1365-2125.2006.02509.x + 10.1111/j.1365-2125.2006.02509.x + self + + + wosuid + WOS:000240556900012 + wos:000240556900012 + self + + + http://gateway.webofknowledge.com/gateway/Gateway.cgi?GWVersion=2&SrcAuth=ORCID&SrcApp=OrcidOrg&DestLinkType=FullRecord&DestApp=WOS_CPL&KeyUT=WOS:000240556900012&KeyUID=WOS:000240556900012 + journal-article + + 2006 + + British Journal of Clinical Pharmacology + + + + 2018-08-23T12:01:11.681Z + + + doi + 10.1097/01.ftd.0000197092.84935.ef + 10.1097/01.ftd.0000197092.84935.ef + self + + + + 2018-08-23T12:01:11.681Z + 2018-08-23T12:01:11.681Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + Determination of mycophenolic acid plasma levels in renal transplant recipients co-administered sirolimus: comparison of an enzyme multiplied immunoassay technique (EMIT) and liquid chromatography-tandem mass spectrometry + + + + doi + 10.1097/01.ftd.0000197092.84935.ef + 10.1097/01.ftd.0000197092.84935.ef + self + + + http://www.ncbi.nlm.nih.gov/pubmed/16628144 + journal-article + + 2006 + + Therapeutic Drug Monitoring + + + + 2018-08-23T12:01:11.683Z + + + doi + 10.2165/00003088-200544080-00005 + 10.2165/00003088-200544080-00005 + self + + + + 2018-08-23T12:01:11.683Z + 2018-08-23T12:01:11.683Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + A double absorption-phase model adequately describes mycophenolic acid plasma profiles in de novo renal transplant recipients given oral mycophenolate mofetil + + + + doi + 10.2165/00003088-200544080-00005 + 10.2165/00003088-200544080-00005 + self + + + http://www.ncbi.nlm.nih.gov/pubmed/16029068 + journal-article + + 2005 + + Clinical Pharmacokinetics + + + + 2018-08-23T12:01:11.686Z + + + wosuid + WOS:000225839800019 + wos:000225839800019 + self + + + doi + 10.1124/dmd.104.001651 + 10.1124/dmd.104.001651 + self + + + + 2018-08-23T12:01:11.686Z + 2018-08-23T12:01:11.686Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + Identification of the UDP-glucuronosyltransferase isoforms involved in mycophenolic acid phase II metabolism + + + + doi + 10.1124/dmd.104.001651 + 10.1124/dmd.104.001651 + self + + + wosuid + WOS:000225839800019 + wos:000225839800019 + self + + + http://gateway.webofknowledge.com/gateway/Gateway.cgi?GWVersion=2&SrcAuth=ORCID&SrcApp=OrcidOrg&DestLinkType=FullRecord&DestApp=WOS_CPL&KeyUT=WOS:000225839800019&KeyUID=WOS:000225839800019 + journal-article + + 2005 + + Drug Metabolism and Disposition: The Biological Fate of Chemicals + + + + 2018-08-23T12:01:11.689Z + + + source-work-id + 0823180801209-17 + 0823180801209-17 + self + + + + 2018-08-23T12:01:11.689Z + 2018-08-23T12:01:11.689Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + Maximum a posteriori bayesian estimation of mycophenolic acid pharmacokinetics in renal transplant recipients at different postgrafting periods + + + + source-work-id + 0823180801209-17 + 0823180801209-17 + self + + + http://www.ncbi.nlm.nih.gov/pubmed/15905807 + journal-article + + 2005 + + Therapeutic Drug Monitoring + + + + 2018-08-23T12:01:11.692Z + + + source-work-id + 0823180801209-19 + 0823180801209-19 + self + + + + 2018-08-23T12:01:11.692Z + 2018-08-23T12:01:11.692Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + Characterization of a phase 1 metabolite of mycophenolic acid produced by CYP3A4/5 + + + + source-work-id + 0823180801209-19 + 0823180801209-19 + self + + + http://www.ncbi.nlm.nih.gov/pubmed/15570183 + journal-article + + 2004 + + Therapeutic Drug Monitoring + + + + 2018-08-23T12:01:11.695Z + + + source-work-id + 0823180801209-18 + 0823180801209-18 + self + + + + 2018-08-23T12:01:11.695Z + 2018-08-23T12:01:11.695Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + Comparison of liquid chromatography-tandem mass spectrometry with a commercial enzyme-multiplied immunoassay for the determination of plasma MPA in renal transplant recipients and consequences for therapeutic drug monitoring + + + + source-work-id + 0823180801209-18 + 0823180801209-18 + self + + + http://www.ncbi.nlm.nih.gov/pubmed/15570184 + journal-article + + 2004 + + Therapeutic Drug Monitoring + + + + 2018-08-23T12:01:11.697Z + + + source-work-id + 0823180801209-21 + 0823180801209-21 + self + + + + 2018-08-23T12:01:11.697Z + 2018-08-23T12:01:11.697Z + + + https://orcid.org/client/0000-0003-1377-5676 + 0000-0003-1377-5676 + orcid.org + + ResearcherID + + https://orcid.org/0000-0001-5004-5918 + 0000-0001-5004-5918 + orcid.org + + Aurélie Prémaud + + + An animal model for the study of chronopharmacokinetics of drugs and application to methotrexate and vinorelbine + + + + source-work-id + 0823180801209-21 + 0823180801209-21 + self + + + http://www.ncbi.nlm.nih.gov/pubmed/12383710 + journal-article + + 2002 + + Toxicology and Applied Pharmacology + + + + + diff --git a/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/xml/record_8888-8888-8888-8880.xml b/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/xml/record_8888-8888-8888-8880.xml index 7abc2f35a..5cf9528c5 100644 --- a/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/xml/record_8888-8888-8888-8880.xml +++ b/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/xml/record_8888-8888-8888-8880.xml @@ -732,7 +732,7 @@ part-of - + 2001-12-31T12:00:00 2001-12-31T12:00:00 From e686b8de8deca03b2645cacdcb3e8724c136ba8c Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Thu, 1 Apr 2021 17:11:03 +0200 Subject: [PATCH 196/445] [ORCID-no-doi] integrating PR#98 https://code-repo.d4science.org/D-Net/dnet-hadoop/pulls/98 --- .../dnetlib/dhp/schema/oaf/Relation.java.rej | 31 -------- .../orcid/SparkDownloadOrcidAuthors.java.rej | 30 -------- .../orcidnodoi/oaf/PublicationToOaf.java.rej | 77 ------------------- 3 files changed, 138 deletions(-) delete mode 100644 dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Relation.java.rej delete mode 100644 dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidAuthors.java.rej delete mode 100644 dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java.rej diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Relation.java.rej b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Relation.java.rej deleted file mode 100644 index 7ce658877..000000000 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Relation.java.rej +++ /dev/null @@ -1,31 +0,0 @@ -diff a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Relation.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/Relation.java (rejected hunks) -@@ -1,8 +1,6 @@ - - package eu.dnetlib.dhp.schema.oaf; - --import eu.dnetlib.dhp.schema.common.ModelSupport; -- - import static com.google.common.base.Preconditions.checkArgument; - - import java.text.ParseException; -@@ -10,6 +8,8 @@ import java.util.*; - import java.util.stream.Collectors; - import java.util.stream.Stream; - -+import eu.dnetlib.dhp.schema.common.ModelSupport; -+ - /** - * Relation models any edge between two nodes in the OpenAIRE graph. It has a source id and a target id pointing to - * graph node identifiers and it is further characterised by the semantic of the link through the fields relType, -@@ -137,7 +137,10 @@ public class Relation extends Oaf { - try { - setValidationDate(ModelSupport.oldest(getValidationDate(), r.getValidationDate())); - } catch (ParseException e) { -- throw new IllegalArgumentException(String.format("invalid validation date format in relation [s:%s, t:%s]: %s", getSource(), getTarget(), getValidationDate())); -+ throw new IllegalArgumentException(String -+ .format( -+ "invalid validation date format in relation [s:%s, t:%s]: %s", getSource(), getTarget(), -+ getValidationDate())); - } - - super.mergeFrom(r); diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidAuthors.java.rej b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidAuthors.java.rej deleted file mode 100644 index fc22d8a7a..000000000 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidAuthors.java.rej +++ /dev/null @@ -1,30 +0,0 @@ -diff a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidAuthors.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkDownloadOrcidAuthors.java (rejected hunks) -@@ -31,7 +32,6 @@ public class SparkDownloadOrcidAuthors { - - static Logger logger = LoggerFactory.getLogger(SparkDownloadOrcidAuthors.class); - static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; -- static String lastUpdate; - - public static void main(String[] args) throws Exception { - -@@ -54,14 +54,18 @@ public class SparkDownloadOrcidAuthors { - final String token = parser.get("token"); - final String lambdaFileName = parser.get("lambdaFileName"); - logger.info("lambdaFileName: {}", lambdaFileName); -- -- lastUpdate = HDFSUtil.readFromTextFile(workingPath.concat("last_update.txt")); -+ final String hdfsServerUri = parser.get("hdfsServerUri"); - - SparkConf conf = new SparkConf(); - runWithSparkSession( - conf, - isSparkSessionManaged, - spark -> { -+ String lastUpdate = HDFSUtil.readFromTextFile(hdfsServerUri, workingPath, "last_update.txt"); -+ logger.info("lastUpdate: ", lastUpdate); -+ if (StringUtils.isBlank(lastUpdate)) { -+ throw new RuntimeException("last update info not found"); -+ } - JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); - - LongAccumulator parsedRecordsAcc = spark.sparkContext().longAccumulator("parsed_records"); diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java.rej b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java.rej deleted file mode 100644 index 76b63a93d..000000000 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java.rej +++ /dev/null @@ -1,77 +0,0 @@ -diff a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcidnodoi/oaf/PublicationToOaf.java (rejected hunks) -@@ -30,11 +30,11 @@ public class PublicationToOaf implements Serializable { - - static Logger logger = LoggerFactory.getLogger(PublicationToOaf.class); - -- public static final String ORCID = "ORCID"; -- public static final String ORCID_PID_TYPE_CLASSNAME = "Open Researcher and Contributor ID"; - public final static String orcidPREFIX = "orcid_______"; - public static final String OPENAIRE_PREFIX = "openaire____"; - public static final String SEPARATOR = "::"; -+ public static final String DEACTIVATED_NAME = "Given Names Deactivated"; -+ public static final String DEACTIVATED_SURNAME = "Family Name Deactivated"; - - private String dateOfCollection = ""; - private final LongAccumulator parsedPublications; -@@ -72,13 +81,18 @@ public class PublicationToOaf implements Serializable { - this.errorsNotFoundAuthors = null; - this.errorsInvalidType = null; - this.otherTypeFound = null; -+ this.deactivatedAcc = null; -+ this.titleNotProvidedAcc = null; -+ this.noUrlAcc = null; - this.dateOfCollection = null; - } - - private static Map> datasources = new HashMap>() { - - { -- put(ORCID.toLowerCase(), new Pair<>(ORCID, OPENAIRE_PREFIX + SEPARATOR + "orcid")); -+ put( -+ ModelConstants.ORCID, -+ new Pair<>(ModelConstants.ORCID.toUpperCase(), OPENAIRE_PREFIX + SEPARATOR + "orcid")); - - } - }; -@@ -183,6 +197,12 @@ public class PublicationToOaf implements Serializable { - } - return null; - } -+ if (titles.stream().filter(t -> (t != null && t.equals("Title Not Supplied"))).count() > 0) { -+ if (titleNotProvidedAcc != null) { -+ titleNotProvidedAcc.add(1); -+ } -+ return null; -+ } - Qualifier q = mapQualifier("main title", "main title", "dnet:dataCite_title", "dnet:dataCite_title"); - publication - .setTitle( -@@ -527,24 +562,21 @@ public class PublicationToOaf implements Serializable { - - private KeyValue createCollectedFrom() { - KeyValue cf = new KeyValue(); -- cf.setValue(ORCID); -+ cf.setValue(ModelConstants.ORCID.toUpperCase()); - cf.setKey("10|" + OPENAIRE_PREFIX + SEPARATOR + "806360c771262b4d6770e7cdf04b5c5a"); - return cf; - } - - private KeyValue createHostedBy() { -- KeyValue hb = new KeyValue(); -- hb.setValue("Unknown Repository"); -- hb.setKey("10|" + OPENAIRE_PREFIX + SEPARATOR + "55045bd2a65019fd8e6741a755395c8c"); -- return hb; -+ return ModelConstants.UNKNOWN_REPOSITORY; - } - - private StructuredProperty mapAuthorId(String orcidId) { - final StructuredProperty sp = new StructuredProperty(); - sp.setValue(orcidId); - final Qualifier q = new Qualifier(); -- q.setClassid(ORCID.toLowerCase()); -- q.setClassname(ORCID_PID_TYPE_CLASSNAME); -+ q.setClassid(ModelConstants.ORCID); -+ q.setClassname(ModelConstants.ORCID_CLASSNAME); - q.setSchemeid(ModelConstants.DNET_PID_TYPES); - q.setSchemename(ModelConstants.DNET_PID_TYPES); - sp.setQualifier(q); From 1e7e5180fa91cc06ca877f49bb82cff6b6814a69 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Fri, 2 Apr 2021 12:32:12 +0200 Subject: [PATCH 197/445] [Graph model] updated definition of ExternalReference: added alternateLabel, removed description (#6503) --- .../dhp/schema/oaf/ExternalReference.java | 27 ++++++++++--------- .../migration/ProtoConverter.java | 1 - .../oa/provision/utils/XmlRecordFactory.java | 13 ++++++--- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/ExternalReference.java b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/ExternalReference.java index d509b954e..0689b44c5 100644 --- a/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/ExternalReference.java +++ b/dhp-schemas/src/main/java/eu/dnetlib/dhp/schema/oaf/ExternalReference.java @@ -2,6 +2,7 @@ package eu.dnetlib.dhp.schema.oaf; import java.io.Serializable; +import java.util.List; import java.util.Objects; public class ExternalReference implements Serializable { @@ -11,12 +12,12 @@ public class ExternalReference implements Serializable { // title private String label; + // alternative labels + private List alternateLabel; + // text() private String url; - // ?? not mapped yet ?? - private String description; - // type private Qualifier qualifier; @@ -45,6 +46,14 @@ public class ExternalReference implements Serializable { this.label = label; } + public List getAlternateLabel() { + return alternateLabel; + } + + public void setAlternateLabel(List alternateLabel) { + this.alternateLabel = alternateLabel; + } + public String getUrl() { return url; } @@ -53,14 +62,6 @@ public class ExternalReference implements Serializable { this.url = url; } - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - public Qualifier getQualifier() { return qualifier; } @@ -103,7 +104,6 @@ public class ExternalReference implements Serializable { return Objects.equals(sitename, that.sitename) && Objects.equals(label, that.label) && Objects.equals(url, that.url) - && Objects.equals(description, that.description) && Objects.equals(qualifier, that.qualifier) && Objects.equals(refidentifier, that.refidentifier) && Objects.equals(query, that.query) @@ -114,6 +114,7 @@ public class ExternalReference implements Serializable { public int hashCode() { return Objects .hash( - sitename, label, url, description, qualifier, refidentifier, query, dataInfo); + sitename, label, url, qualifier, refidentifier, query, dataInfo); } + } diff --git a/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/migration/ProtoConverter.java b/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/migration/ProtoConverter.java index 2478da0e9..70f37fba3 100644 --- a/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/migration/ProtoConverter.java +++ b/dhp-workflows/dhp-actionmanager/src/main/java/eu/dnetlib/dhp/actionmanager/migration/ProtoConverter.java @@ -132,7 +132,6 @@ public class ProtoConverter implements Serializable { ex.setQuery(e.getQuery()); ex.setQualifier(mapQualifier(e.getQualifier())); ex.setLabel(e.getLabel()); - ex.setDescription(e.getDescription()); ex.setDataInfo(ex.getDataInfo()); return ex; } diff --git a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/utils/XmlRecordFactory.java b/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/utils/XmlRecordFactory.java index b9d450c75..644ed98ab 100644 --- a/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/utils/XmlRecordFactory.java +++ b/dhp-workflows/dhp-graph-provision/src/main/java/eu/dnetlib/dhp/oa/provision/utils/XmlRecordFactory.java @@ -45,7 +45,6 @@ import eu.dnetlib.dhp.schema.oaf.Result; public class XmlRecordFactory implements Serializable { - private static final String REL_SUBTYPE_DEDUP = "dedup"; private final Map accumulators; private final Set specialDatasourceTypes; @@ -1202,12 +1201,18 @@ public class XmlRecordFactory implements Serializable { if (isNotBlank(er.getLabel())) { fields.add(XmlSerializationUtils.asXmlElement("label", er.getLabel())); } + Optional + .ofNullable(er.getAlternateLabel()) + .map( + altLabel -> altLabel + .stream() + .filter(StringUtils::isNotBlank) + .collect(Collectors.toList())) + .orElse(Lists.newArrayList()) + .forEach(alt -> fields.add(XmlSerializationUtils.asXmlElement("alternatelabel", alt))); if (isNotBlank(er.getUrl())) { fields.add(XmlSerializationUtils.asXmlElement("url", er.getUrl())); } - if (isNotBlank(er.getDescription())) { - fields.add(XmlSerializationUtils.asXmlElement("description", er.getDescription())); - } if (isNotBlank(er.getUrl())) { fields.add(XmlSerializationUtils.mapQualifier("qualifier", er.getQualifier())); } From c39c82dfe9c47465e8adfb10038d3cddb1b32fc9 Mon Sep 17 00:00:00 2001 From: miconis Date: Tue, 6 Apr 2021 14:31:00 +0200 Subject: [PATCH 198/445] modification of the jobs for the integration of openorgs in the provision, dedup records are no more created by merging but simply taking results of openorgs portal --- .../oa/dedup/SparkCopyOpenorgsMergeRels.java | 76 ++----------------- ...s.java => SparkCreateOrgsDedupRecord.java} | 65 ++++++++++------ .../dhp/oa/dedup/SparkUpdateEntity.java | 26 +++++-- .../dhp/oa/dedup/scan/oozie_app/workflow.xml | 31 +------- .../dhp/oa/dedup/SparkOpenorgsTest.java | 11 ++- .../raw/MigrateDbEntitiesApplication.java | 4 +- .../dhp/oa/graph/sql/queryOrganizations.sql | 5 +- 7 files changed, 87 insertions(+), 131 deletions(-) rename dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/{SparkCopyOpenorgs.java => SparkCreateOrgsDedupRecord.java} (58%) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java index dd74677bc..eaa00c4b8 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java @@ -78,42 +78,16 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { final String relationPath = DedupUtility.createEntityPath(graphBasePath, "relation"); - DedupConfig dedupConf = getConfigurations(isLookUpService, actionSetId).get(0); - - JavaRDD rawRels = spark + //collect organization merge relations from openorgs database + JavaRDD mergeRelsRDD = spark .read() .textFile(relationPath) .map(patchRelFn(), Encoders.bean(Relation.class)) .toJavaRDD() - .filter(this::isOpenorgs) - .filter(this::filterOpenorgsRels); + .filter(this::isOpenorgs) //take only openorgs relations + .filter(this::isMergeRel); //take merges and isMergedIn relations - JavaRDD selfRawRels = rawRels - .map(r -> r.getSource()) - .distinct() - .map(s -> rel(s, s, ModelConstants.IS_SIMILAR_TO, dedupConf)); - - log.info("Number of raw Openorgs Relations collected: {}", rawRels.count()); - - // turn openorgs isSimilarTo relations into mergerels - JavaRDD mergeRelsRDD = rawRels - .union(selfRawRels) - .map(r -> { - r.setSource(createDedupID(r.getSource())); // create the dedup_id to align it to the openaire dedup - // format - return r; - }) - .flatMap(rel -> { - - List mergerels = new ArrayList<>(); - - mergerels.add(rel(rel.getSource(), rel.getTarget(), ModelConstants.MERGES, dedupConf)); - mergerels.add(rel(rel.getTarget(), rel.getSource(), ModelConstants.IS_MERGED_IN, dedupConf)); - - return mergerels.iterator(); - }); - - log.info("Number of Openorgs Merge Relations created: {}", mergeRelsRDD.count()); + log.info("Number of Openorgs Merge Relations collected: {}", mergeRelsRDD.count()); spark .createDataset( @@ -124,45 +98,9 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { .parquet(outputPath); } - private boolean filterOpenorgsRels(Relation rel) { - return rel.getRelClass().equals(ModelConstants.IS_SIMILAR_TO) + private boolean isMergeRel(Relation rel) { + return (rel.getRelClass().equals(ModelConstants.MERGES) || rel.getRelClass().equals(ModelConstants.IS_MERGED_IN)) && rel.getRelType().equals(ModelConstants.ORG_ORG_RELTYPE) && rel.getSubRelType().equals(ModelConstants.DEDUP); } - - private Relation rel(String source, String target, String relClass, DedupConfig dedupConf) { - - String entityType = dedupConf.getWf().getEntityType(); - - Relation r = new Relation(); - r.setSource(source); - r.setTarget(target); - r.setRelClass(relClass); - r.setRelType(entityType + entityType.substring(0, 1).toUpperCase() + entityType.substring(1)); - r.setSubRelType(ModelConstants.DEDUP); - - DataInfo info = new DataInfo(); - info.setDeletedbyinference(false); - info.setInferred(true); - info.setInvisible(false); - info.setInferenceprovenance(dedupConf.getWf().getConfigurationId()); - Qualifier provenanceAction = new Qualifier(); - provenanceAction.setClassid(ModelConstants.PROVENANCE_DEDUP); - provenanceAction.setClassname(ModelConstants.PROVENANCE_DEDUP); - provenanceAction.setSchemeid(ModelConstants.DNET_PROVENANCE_ACTIONS); - provenanceAction.setSchemename(ModelConstants.DNET_PROVENANCE_ACTIONS); - info.setProvenanceaction(provenanceAction); - - // TODO calculate the trust value based on the similarity score of the elements in the CC - // info.setTrust(); - - r.setDataInfo(info); - return r; - } - - public String createDedupID(String id) { - - String prefix = id.split("\\|")[0]; - return prefix + "|dedup_wf_001::" + DHPUtils.md5(id); - } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateOrgsDedupRecord.java similarity index 58% rename from dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java rename to dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateOrgsDedupRecord.java index 543558f36..38f9f03e8 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgs.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateOrgsDedupRecord.java @@ -4,8 +4,10 @@ package eu.dnetlib.dhp.oa.dedup; import java.io.IOException; import java.util.Optional; +import eu.dnetlib.dhp.schema.oaf.Relation; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaPairRDD; import org.apache.spark.api.java.JavaSparkContext; import org.apache.spark.api.java.function.MapFunction; import org.apache.spark.sql.Dataset; @@ -24,11 +26,12 @@ import eu.dnetlib.dhp.schema.oaf.Organization; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import scala.Tuple2; -public class SparkCopyOpenorgs extends AbstractSparkAction { - private static final Logger log = LoggerFactory.getLogger(SparkCopyOpenorgs.class); +public class SparkCreateOrgsDedupRecord extends AbstractSparkAction { + private static final Logger log = LoggerFactory.getLogger(SparkCreateOrgsDedupRecord.class); - public SparkCopyOpenorgs(ArgumentApplicationParser parser, SparkSession spark) { + public SparkCreateOrgsDedupRecord(ArgumentApplicationParser parser, SparkSession spark) { super(parser, spark); } @@ -36,13 +39,13 @@ public class SparkCopyOpenorgs extends AbstractSparkAction { ArgumentApplicationParser parser = new ArgumentApplicationParser( IOUtils .toString( - SparkCopyOpenorgs.class + SparkCreateOrgsDedupRecord.class .getResourceAsStream( "/eu/dnetlib/dhp/oa/dedup/copyOpenorgs_parameters.json"))); parser.parseArgument(args); SparkConf conf = new SparkConf(); - new SparkCopyOpenorgs(parser, getSparkSession(conf)) + new SparkCreateOrgsDedupRecord(parser, getSparkSession(conf)) .run(ISLookupClientFactory.getLookUpService(parser.get("isLookUpUrl"))); } @@ -64,14 +67,15 @@ public class SparkCopyOpenorgs extends AbstractSparkAction { log.info("actionSetId: '{}'", actionSetId); log.info("workingPath: '{}'", workingPath); - String subEntity = "organization"; - log.info("Copying openorgs to the working dir"); + log.info("Copying organization dedup records to the working dir"); - final String outputPath = DedupUtility.createDedupRecordPath(workingPath, actionSetId, subEntity); + final String outputPath = DedupUtility.createDedupRecordPath(workingPath, actionSetId, "organization"); - final String entityPath = DedupUtility.createEntityPath(graphBasePath, subEntity); + final String entityPath = DedupUtility.createEntityPath(graphBasePath, "organization"); - filterOpenorgs(spark, entityPath) + final String mergeRelsPath = DedupUtility.createMergeRelPath(workingPath, actionSetId, "organization"); + + rootOrganization(spark, entityPath, mergeRelsPath) .write() .mode(SaveMode.Overwrite) .option("compression", "gzip") @@ -79,26 +83,43 @@ public class SparkCopyOpenorgs extends AbstractSparkAction { } - public static Dataset filterOpenorgs( + public static Dataset rootOrganization( final SparkSession spark, - final String entitiesInputPath) { + final String entitiesInputPath, + final String mergeRelsPath) { JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); - Dataset entities = spark - .createDataset( - sc - .textFile(entitiesInputPath) - .map(it -> OBJECT_MAPPER.readValue(it, Organization.class)) - .rdd(), - Encoders.bean(Organization.class)); + + JavaPairRDD entities = sc.textFile(entitiesInputPath) + .map(it -> OBJECT_MAPPER.readValue(it, Organization.class)) + .mapToPair(o -> new Tuple2<>(o.getId(), o)); log.info("Number of organization entities processed: {}", entities.count()); - entities = entities.filter(entities.col("id").contains(DedupUtility.OPENORGS_ID_PREFIX)); + //collect root ids (ids in the source of 'merges' relations + JavaPairRDD roots = spark + .read() + .load(mergeRelsPath) + .as(Encoders.bean(Relation.class)) + .where("relClass == 'merges'") + .map( + (MapFunction>) r -> new Tuple2<>(r.getSource(), "root"), + Encoders.tuple(Encoders.STRING(), Encoders.STRING())) + .toJavaRDD() + .mapToPair(t -> t) + .distinct(); - log.info("Number of Openorgs organization entities: {}", entities.count()); + Dataset rootOrgs = spark.createDataset( + entities + .leftOuterJoin(roots) + .filter(e -> e._2()._2().isPresent()) //if it has been joined with 'root' then it's a root record + .map(e -> e._2()._1()) + .rdd(), + Encoders.bean(Organization.class)); - return entities; + log.info("Number of Root organization: {}", entities.count()); + + return rootOrgs; } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkUpdateEntity.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkUpdateEntity.java index 5ebc00d5a..1e13d0111 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkUpdateEntity.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkUpdateEntity.java @@ -101,6 +101,9 @@ public class SparkUpdateEntity extends AbstractSparkAction { .mapToPair( (PairFunction) s -> new Tuple2<>( MapDocumentUtil.getJPathString(IDJSONPATH, s), s)); + if (type == EntityType.organization) //exclude root records from organizations + entitiesWithId = excludeRootOrgs(entitiesWithId, rel); + JavaRDD map = entitiesWithId .leftOuterJoin(mergedIds) .map(k -> { @@ -110,13 +113,6 @@ public class SparkUpdateEntity extends AbstractSparkAction { return k._2()._1(); }); - if (type == EntityType.organization) // exclude openorgs with deletedbyinference=true - map = map.filter(it -> { - Organization org = OBJECT_MAPPER.readValue(it, Organization.class); - return !org.getId().contains("openorgs____") || (org.getId().contains("openorgs____") - && !org.getDataInfo().getDeletedbyinference()); - }); - sourceEntity = map.union(sc.textFile(dedupRecordPath)); } @@ -159,4 +155,20 @@ public class SparkUpdateEntity extends AbstractSparkAction { throw new RuntimeException("Unable to convert json", e); } } + + private static JavaPairRDD excludeRootOrgs(JavaPairRDD entitiesWithId, Dataset rel) { + + JavaPairRDD roots = rel + .where("relClass == 'merges'") + .select(rel.col("source")) + .distinct() + .toJavaRDD() + .mapToPair( + (PairFunction) r -> new Tuple2<>(r.getString(0), "root")); + + return entitiesWithId + .leftOuterJoin(roots) + .filter(e -> !e._2()._2().isPresent()) + .mapToPair(e -> new Tuple2<>(e._1(), e._2()._1())); + } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml index 4b39cb56a..b86bc009c 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml @@ -187,7 +187,7 @@ - + yarn @@ -220,7 +220,7 @@ yarn cluster Create Organizations Dedup Records - eu.dnetlib.dhp.oa.dedup.SparkCreateDedupRecord + eu.dnetlib.dhp.oa.dedup.SparkCreateOrgsDedupRecord dhp-dedup-openaire-${projectVersion}.jar --executor-memory=${sparkExecutorMemory} @@ -241,33 +241,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - yarn diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java index 419be1da3..ac2b29658 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java @@ -12,10 +12,14 @@ import java.io.Serializable; import java.net.URISyntaxException; import java.nio.file.Paths; +import eu.dnetlib.dhp.schema.oaf.Relation; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaSparkContext; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Encoders; +import org.apache.spark.sql.Row; import org.apache.spark.sql.SparkSession; import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.ExtendWith; @@ -148,9 +152,14 @@ public class SparkOpenorgsTest implements Serializable { long orgs_mergerel = spark .read() - .load(testOutputBasePath + "/" + testActionSetId + "/organization_mergerel") + .load(DedupUtility.createMergeRelPath(testOutputBasePath, testActionSetId, "organization")) .count(); + Dataset orgrels = spark.read().load(DedupUtility.createMergeRelPath(testOutputBasePath, testActionSetId, "organization")).as(Encoders.bean(Relation.class)); + + for (Relation r: orgrels.toJavaRDD().collect()) + System.out.println("r = " + r.getSource() + "---" + r.getTarget() + "---" + r.getRelClass()); + assertEquals(384, orgs_mergerel); } diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java index 19dcde3bd..270c90913 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java @@ -180,14 +180,14 @@ public class MigrateDbEntitiesApplication extends AbstractMigrationApplication i log.info("Processing Openorgs Merge Rels..."); smdbe.execute("queryOpenOrgsSimilarityForProvision.sql", smdbe::processOrgOrgSimRels); - + //TODO cambiare il mapping delle relazioni in modo che crei merges e isMergedIn + // TODO (specifico per questo caso, questa funzione di mapping verrà usata così com'è nel caso di openorgs dedup break; case openaire_organizations: log.info("Processing Organizations..."); smdbe.execute("queryOrganizations.sql", smdbe::processOrganization, verifyNamespacePrefix); - break; } log.info("All done."); diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizations.sql b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizations.sql index 42ba0cf91..73e39f8fa 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizations.sql +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizations.sql @@ -50,4 +50,7 @@ GROUP BY o.trust, d.id, d.officialname, - o.country; \ No newline at end of file + o.country; + +-- TODO modificare in modo da fare il merge dei pid di tutti i record mergiati (per gli openorgs, approvati) +-- TODO invece per tutti gli altri con dei duplicati non fare questa cosa \ No newline at end of file From eaaefb8b4ce3523fdec4f57df44db316167a011c Mon Sep 17 00:00:00 2001 From: miconis Date: Tue, 6 Apr 2021 14:35:51 +0200 Subject: [PATCH 199/445] implementation of the procedure to reuse content of different dbs when creating the raw graph --- .../raw/MigrateDbEntitiesApplication.java | 4 +- .../oa/graph/raw_all/oozie_app/workflow.xml | 148 +++++++++++++----- 2 files changed, 109 insertions(+), 43 deletions(-) diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java index 270c90913..f61ab779d 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java @@ -161,7 +161,7 @@ public class MigrateDbEntitiesApplication extends AbstractMigrationApplication i .execute( "queryProjectOrganization.sql", smdbe::processProjectOrganization, verifyNamespacePrefix); break; - case openorgs_dedup: + case openorgs_dedup: //generates organization entities and relations for openorgs dedup log.info("Processing Openorgs..."); smdbe .execute( @@ -172,7 +172,7 @@ public class MigrateDbEntitiesApplication extends AbstractMigrationApplication i break; - case openorgs: + case openorgs: //generates organization entities and relations for provision log.info("Processing Openorgs For Provision..."); smdbe .execute( diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_all/oozie_app/workflow.xml b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_all/oozie_app/workflow.xml index ea12171e9..80f33bd53 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_all/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/raw_all/oozie_app/workflow.xml @@ -6,14 +6,39 @@ the target path to store raw graph - reuseContent + reuseDBClaims false should import content from the aggregator or reuse a previous version - importOpenorgs - true - should import content from the OpenOrgs database + reuseODFClaims + false + should import content from the aggregator or reuse a previous version + + + reuseOAFClaims + false + should import content from the aggregator or reuse a previous version + + + reuseDB + false + should import content from the aggregator or reuse a previous version + + + reuseDBOpenorgs + false + should import content from the aggregator or reuse a previous version + + + reuseODF + false + should import content from the aggregator or reuse a previous version + + + reuseOAF + false + should import content from the aggregator or reuse a previous version contentPath @@ -120,25 +145,26 @@ - + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] - + + + + + + + - ${wf:conf('reuseContent') eq false} - ${wf:conf('reuseContent') eq true} - + ${wf:conf('reuseDBClaims') eq false} + ${wf:conf('reuseDBClaims') eq true} + - - - - - @@ -154,10 +180,18 @@ --dbschema${dbSchema} --nsPrefixBlacklist${nsPrefixBlacklist} - + + + + ${wf:conf('reuseODFClaims') eq false} + ${wf:conf('reuseODFClaims') eq true} + + + + @@ -171,10 +205,18 @@ -lstore -iclaim - + + + + ${wf:conf('reuseOAFClaims') eq false} + ${wf:conf('reuseOAFClaims') eq true} + + + + @@ -192,6 +234,14 @@ + + + ${wf:conf('reuseDB') eq false} + ${wf:conf('reuseDB') eq true} + + + + @@ -207,37 +257,18 @@ --dbschema${dbSchema} --nsPrefixBlacklist${nsPrefixBlacklist} - + - + - ${wf:conf('importOpenorgs') eq true} - ${wf:conf('importOpenorgs') eq false} - + ${wf:conf('reuseODF') eq false} + ${wf:conf('reuseODF') eq true} + - - - - - - eu.dnetlib.dhp.oa.graph.raw.MigrateDbEntitiesApplication - --hdfsPath${contentPath}/db_openorgs - --postgresUrl${postgresOpenOrgsURL} - --postgresUser${postgresOpenOrgsUser} - --postgresPassword${postgresOpenOrgsPassword} - --isLookupUrl${isLookupUrl} - --actionopenorgs - --dbschema${dbSchema} - --nsPrefixBlacklist${nsPrefixBlacklist} - - - - - @@ -251,10 +282,18 @@ --mdLayoutstore --mdInterpretationcleaned - + + + + ${wf:conf('reuseOAF') eq false} + ${wf:conf('reuseOAF') eq true} + + + + @@ -289,6 +328,33 @@ + + + ${wf:conf('reuseDBOpenorgs') eq false} + ${wf:conf('reuseDBOpenorgs') eq true} + + + + + + + + + + eu.dnetlib.dhp.oa.graph.raw.MigrateDbEntitiesApplication + --hdfsPath${contentPath}/db_openorgs + --postgresUrl${postgresOpenOrgsURL} + --postgresUser${postgresOpenOrgsUser} + --postgresPassword${postgresOpenOrgsPassword} + --isLookupUrl${isLookupUrl} + --actionopenorgs + --dbschema${dbSchema} + --nsPrefixBlacklist${nsPrefixBlacklist} + + + + + From bf685d849f332c84c561d3a36c3aac2a695ad687 Mon Sep 17 00:00:00 2001 From: miconis Date: Wed, 7 Apr 2021 14:27:43 +0200 Subject: [PATCH 200/445] addition of pids in the query for the export of openorgs for the provision, addition of ec_fields in the openorgs model --- .../dhp/oa/dedup/AbstractSparkAction.java | 8 ++ .../oa/dedup/SparkCopyOpenorgsMergeRels.java | 9 +- .../oa/dedup/SparkCreateOrgsDedupRecord.java | 42 ++++--- .../dhp/oa/dedup/SparkPrepareNewOrgs.java | 12 +- .../dhp/oa/dedup/SparkPrepareOrgRels.java | 24 +++- .../dhp/oa/dedup/SparkUpdateEntity.java | 23 ++-- .../dnetlib/dhp/oa/dedup/model/OrgSimRel.java | 116 +++++++++++++++++- .../dhp/oa/dedup/SparkOpenorgsTest.java | 37 +----- .../raw/MigrateDbEntitiesApplication.java | 71 ++++++----- .../graph/sql/queryOpenOrgsForOrgsDedup.sql | 22 ++-- .../graph/sql/queryOpenOrgsForProvision.sql | 25 ++-- .../queryOpenOrgsSimilarityForProvision.sql | 3 +- .../dhp/oa/graph/sql/queryOrganizations.sql | 5 +- 13 files changed, 270 insertions(+), 127 deletions(-) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java index 68211fd28..e8e67567b 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java @@ -142,4 +142,12 @@ abstract class AbstractSparkAction implements Serializable { .isPresent()) .orElse(false); } + + protected static Boolean parseECField(Field field) { + if (field == null) + return null; + if (StringUtils.isBlank(field.getValue()) || field.getValue().equalsIgnoreCase("null")) + return null; + return field.getValue().equalsIgnoreCase("true"); + } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java index eaa00c4b8..bd330ba87 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCopyOpenorgsMergeRels.java @@ -78,14 +78,14 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { final String relationPath = DedupUtility.createEntityPath(graphBasePath, "relation"); - //collect organization merge relations from openorgs database + // collect organization merge relations from openorgs database JavaRDD mergeRelsRDD = spark .read() .textFile(relationPath) .map(patchRelFn(), Encoders.bean(Relation.class)) .toJavaRDD() - .filter(this::isOpenorgs) //take only openorgs relations - .filter(this::isMergeRel); //take merges and isMergedIn relations + .filter(this::isOpenorgs) // take only openorgs relations + .filter(this::isMergeRel); // take merges and isMergedIn relations log.info("Number of Openorgs Merge Relations collected: {}", mergeRelsRDD.count()); @@ -99,7 +99,8 @@ public class SparkCopyOpenorgsMergeRels extends AbstractSparkAction { } private boolean isMergeRel(Relation rel) { - return (rel.getRelClass().equals(ModelConstants.MERGES) || rel.getRelClass().equals(ModelConstants.IS_MERGED_IN)) + return (rel.getRelClass().equals(ModelConstants.MERGES) + || rel.getRelClass().equals(ModelConstants.IS_MERGED_IN)) && rel.getRelType().equals(ModelConstants.ORG_ORG_RELTYPE) && rel.getSubRelType().equals(ModelConstants.DEDUP); } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateOrgsDedupRecord.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateOrgsDedupRecord.java index 38f9f03e8..df3db7add 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateOrgsDedupRecord.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkCreateOrgsDedupRecord.java @@ -4,7 +4,6 @@ package eu.dnetlib.dhp.oa.dedup; import java.io.IOException; import java.util.Optional; -import eu.dnetlib.dhp.schema.oaf.Relation; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaPairRDD; @@ -23,6 +22,7 @@ import eu.dnetlib.dhp.schema.common.EntityType; import eu.dnetlib.dhp.schema.common.ModelSupport; import eu.dnetlib.dhp.schema.oaf.OafEntity; import eu.dnetlib.dhp.schema.oaf.Organization; +import eu.dnetlib.dhp.schema.oaf.Relation; import eu.dnetlib.dhp.utils.ISLookupClientFactory; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; @@ -90,31 +90,33 @@ public class SparkCreateOrgsDedupRecord extends AbstractSparkAction { JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext()); - JavaPairRDD entities = sc.textFile(entitiesInputPath) - .map(it -> OBJECT_MAPPER.readValue(it, Organization.class)) - .mapToPair(o -> new Tuple2<>(o.getId(), o)); + JavaPairRDD entities = sc + .textFile(entitiesInputPath) + .map(it -> OBJECT_MAPPER.readValue(it, Organization.class)) + .mapToPair(o -> new Tuple2<>(o.getId(), o)); log.info("Number of organization entities processed: {}", entities.count()); - //collect root ids (ids in the source of 'merges' relations + // collect root ids (ids in the source of 'merges' relations JavaPairRDD roots = spark - .read() - .load(mergeRelsPath) - .as(Encoders.bean(Relation.class)) - .where("relClass == 'merges'") - .map( - (MapFunction>) r -> new Tuple2<>(r.getSource(), "root"), - Encoders.tuple(Encoders.STRING(), Encoders.STRING())) - .toJavaRDD() - .mapToPair(t -> t) - .distinct(); + .read() + .load(mergeRelsPath) + .as(Encoders.bean(Relation.class)) + .where("relClass == 'merges'") + .map( + (MapFunction>) r -> new Tuple2<>(r.getSource(), "root"), + Encoders.tuple(Encoders.STRING(), Encoders.STRING())) + .toJavaRDD() + .mapToPair(t -> t) + .distinct(); - Dataset rootOrgs = spark.createDataset( + Dataset rootOrgs = spark + .createDataset( entities - .leftOuterJoin(roots) - .filter(e -> e._2()._2().isPresent()) //if it has been joined with 'root' then it's a root record - .map(e -> e._2()._1()) - .rdd(), + .leftOuterJoin(roots) + .filter(e -> e._2()._2().isPresent()) // if it has been joined with 'root' then it's a root record + .map(e -> e._2()._1()) + .rdd(), Encoders.bean(Organization.class)); log.info("Number of Root organization: {}", entities.count()); diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java index 94aab20cc..52ef4b39f 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareNewOrgs.java @@ -183,7 +183,17 @@ public class SparkPrepareNewOrgs extends AbstractSparkAction { r._1()._2().getWebsiteurl() != null ? r._1()._2().getWebsiteurl().getValue() : "", r._1()._2().getCollectedfrom().get(0).getValue(), "", - structuredPropertyListToString(r._1()._2().getPid())), + structuredPropertyListToString(r._1()._2().getPid()), + parseECField(r._1()._2().getEclegalbody()), + parseECField(r._1()._2().getEclegalperson()), + parseECField(r._1()._2().getEcnonprofit()), + parseECField(r._1()._2().getEcresearchorganization()), + parseECField(r._1()._2().getEchighereducation()), + parseECField(r._1()._2().getEcinternationalorganizationeurinterests()), + parseECField(r._1()._2().getEcinternationalorganization()), + parseECField(r._1()._2().getEcenterprise()), + parseECField(r._1()._2().getEcsmevalidated()), + parseECField(r._1()._2().getEcnutscode())), Encoders.bean(OrgSimRel.class)); } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java index 53e6724ba..08b39793e 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkPrepareOrgRels.java @@ -224,7 +224,17 @@ public class SparkPrepareOrgRels extends AbstractSparkAction { .map(c -> Optional.ofNullable(c.get(0)).map(KeyValue::getValue).orElse("")) .orElse(""), r._1()._3(), - structuredPropertyListToString(o.getPid())); + structuredPropertyListToString(o.getPid()), + parseECField(o.getEclegalbody()), + parseECField(o.getEclegalperson()), + parseECField(o.getEcnonprofit()), + parseECField(o.getEcresearchorganization()), + parseECField(o.getEchighereducation()), + parseECField(o.getEcinternationalorganizationeurinterests()), + parseECField(o.getEcinternationalorganization()), + parseECField(o.getEcenterprise()), + parseECField(o.getEcsmevalidated()), + parseECField(o.getEcnutscode())); }, Encoders.bean(OrgSimRel.class)) .map( @@ -301,7 +311,17 @@ public class SparkPrepareOrgRels extends AbstractSparkAction { r._2()._2().getWebsiteurl() != null ? r._2()._2().getWebsiteurl().getValue() : "", r._2()._2().getCollectedfrom().get(0).getValue(), "group::" + r._1()._1(), - structuredPropertyListToString(r._2()._2().getPid())), + structuredPropertyListToString(r._2()._2().getPid()), + parseECField(r._2()._2().getEclegalbody()), + parseECField(r._2()._2().getEclegalperson()), + parseECField(r._2()._2().getEcnonprofit()), + parseECField(r._2()._2().getEcresearchorganization()), + parseECField(r._2()._2().getEchighereducation()), + parseECField(r._2()._2().getEcinternationalorganizationeurinterests()), + parseECField(r._2()._2().getEcinternationalorganization()), + parseECField(r._2()._2().getEcenterprise()), + parseECField(r._2()._2().getEcsmevalidated()), + parseECField(r._2()._2().getEcnutscode())), Encoders.bean(OrgSimRel.class)) .map( (MapFunction>) o -> new Tuple2<>(o.getLocal_id(), o), diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkUpdateEntity.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkUpdateEntity.java index 1e13d0111..cda4137ba 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkUpdateEntity.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkUpdateEntity.java @@ -101,7 +101,7 @@ public class SparkUpdateEntity extends AbstractSparkAction { .mapToPair( (PairFunction) s -> new Tuple2<>( MapDocumentUtil.getJPathString(IDJSONPATH, s), s)); - if (type == EntityType.organization) //exclude root records from organizations + if (type == EntityType.organization) // exclude root records from organizations entitiesWithId = excludeRootOrgs(entitiesWithId, rel); JavaRDD map = entitiesWithId @@ -156,19 +156,20 @@ public class SparkUpdateEntity extends AbstractSparkAction { } } - private static JavaPairRDD excludeRootOrgs(JavaPairRDD entitiesWithId, Dataset rel) { + private static JavaPairRDD excludeRootOrgs(JavaPairRDD entitiesWithId, + Dataset rel) { JavaPairRDD roots = rel - .where("relClass == 'merges'") - .select(rel.col("source")) - .distinct() - .toJavaRDD() - .mapToPair( - (PairFunction) r -> new Tuple2<>(r.getString(0), "root")); + .where("relClass == 'merges'") + .select(rel.col("source")) + .distinct() + .toJavaRDD() + .mapToPair( + (PairFunction) r -> new Tuple2<>(r.getString(0), "root")); return entitiesWithId - .leftOuterJoin(roots) - .filter(e -> !e._2()._2().isPresent()) - .mapToPair(e -> new Tuple2<>(e._1(), e._2()._1())); + .leftOuterJoin(roots) + .filter(e -> !e._2()._2().isPresent()) + .mapToPair(e -> new Tuple2<>(e._1(), e._2()._1())); } } diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/OrgSimRel.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/OrgSimRel.java index adff1ab8a..a4784dd12 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/OrgSimRel.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/model/OrgSimRel.java @@ -14,12 +14,25 @@ public class OrgSimRel implements Serializable { String oa_collectedfrom; String group_id; String pid_list; // separator for type-pid: "###"; separator for pids: "@@@" + Boolean ec_legalbody; + Boolean ec_legalperson; + Boolean ec_nonprofit; + Boolean ec_researchorganization; + Boolean ec_highereducation; + Boolean ec_internationalorganizationeurinterests; + Boolean ec_internationalorganization; + Boolean ec_enterprise; + Boolean ec_smevalidated; + Boolean ec_nutscode; public OrgSimRel() { } public OrgSimRel(String local_id, String oa_original_id, String oa_name, String oa_acronym, String oa_country, - String oa_url, String oa_collectedfrom, String group_id, String pid_list) { + String oa_url, String oa_collectedfrom, String group_id, String pid_list, Boolean ec_legalbody, + Boolean ec_legalperson, Boolean ec_nonprofit, Boolean ec_researchorganization, Boolean ec_highereducation, + Boolean ec_internationalorganizationeurinterests, Boolean ec_internationalorganization, Boolean ec_enterprise, + Boolean ec_smevalidated, Boolean ec_nutscode) { this.local_id = local_id; this.oa_original_id = oa_original_id; this.oa_name = oa_name; @@ -29,6 +42,16 @@ public class OrgSimRel implements Serializable { this.oa_collectedfrom = oa_collectedfrom; this.group_id = group_id; this.pid_list = pid_list; + this.ec_legalbody = ec_legalbody; + this.ec_legalperson = ec_legalperson; + this.ec_nonprofit = ec_nonprofit; + this.ec_researchorganization = ec_researchorganization; + this.ec_highereducation = ec_highereducation; + this.ec_internationalorganizationeurinterests = ec_internationalorganizationeurinterests; + this.ec_internationalorganization = ec_internationalorganization; + this.ec_enterprise = ec_enterprise; + this.ec_smevalidated = ec_smevalidated; + this.ec_nutscode = ec_nutscode; } public String getLocal_id() { @@ -103,6 +126,86 @@ public class OrgSimRel implements Serializable { this.pid_list = pid_list; } + public Boolean getEc_legalbody() { + return ec_legalbody; + } + + public void setEc_legalbody(Boolean ec_legalbody) { + this.ec_legalbody = ec_legalbody; + } + + public Boolean getEc_legalperson() { + return ec_legalperson; + } + + public void setEc_legalperson(Boolean ec_legalperson) { + this.ec_legalperson = ec_legalperson; + } + + public Boolean getEc_nonprofit() { + return ec_nonprofit; + } + + public void setEc_nonprofit(Boolean ec_nonprofit) { + this.ec_nonprofit = ec_nonprofit; + } + + public Boolean getEc_researchorganization() { + return ec_researchorganization; + } + + public void setEc_researchorganization(Boolean ec_researchorganization) { + this.ec_researchorganization = ec_researchorganization; + } + + public Boolean getEc_highereducation() { + return ec_highereducation; + } + + public void setEc_highereducation(Boolean ec_highereducation) { + this.ec_highereducation = ec_highereducation; + } + + public Boolean getEc_internationalorganizationeurinterests() { + return ec_internationalorganizationeurinterests; + } + + public void setEc_internationalorganizationeurinterests(Boolean ec_internationalorganizationeurinterests) { + this.ec_internationalorganizationeurinterests = ec_internationalorganizationeurinterests; + } + + public Boolean getEc_internationalorganization() { + return ec_internationalorganization; + } + + public void setEc_internationalorganization(Boolean ec_internationalorganization) { + this.ec_internationalorganization = ec_internationalorganization; + } + + public Boolean getEc_enterprise() { + return ec_enterprise; + } + + public void setEc_enterprise(Boolean ec_enterprise) { + this.ec_enterprise = ec_enterprise; + } + + public Boolean getEc_smevalidated() { + return ec_smevalidated; + } + + public void setEc_smevalidated(Boolean ec_smevalidated) { + this.ec_smevalidated = ec_smevalidated; + } + + public Boolean getEc_nutscode() { + return ec_nutscode; + } + + public void setEc_nutscode(Boolean ec_nutscode) { + this.ec_nutscode = ec_nutscode; + } + @Override public String toString() { return "OrgSimRel{" + @@ -115,6 +218,17 @@ public class OrgSimRel implements Serializable { ", oa_collectedfrom='" + oa_collectedfrom + '\'' + ", group_id='" + group_id + '\'' + ", pid_list='" + pid_list + '\'' + + ", ec_legalbody=" + ec_legalbody + + ", ec_legalperson=" + ec_legalperson + + ", ec_nonprofit=" + ec_nonprofit + + ", ec_researchorganization=" + ec_researchorganization + + ", ec_highereducation=" + ec_highereducation + + ", ec_internationalorganizationeurinterests=" + ec_internationalorganizationeurinterests + + ", ec_internationalorganization=" + ec_internationalorganization + + ", ec_enterprise=" + ec_enterprise + + ", ec_smevalidated=" + ec_smevalidated + + ", ec_nutscode=" + ec_nutscode + '}'; } + } diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java index ac2b29658..fa1fd63bf 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java @@ -12,7 +12,6 @@ import java.io.Serializable; import java.net.URISyntaxException; import java.nio.file.Paths; -import eu.dnetlib.dhp.schema.oaf.Relation; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; @@ -31,6 +30,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import eu.dnetlib.dhp.application.ArgumentApplicationParser; +import eu.dnetlib.dhp.schema.oaf.Relation; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; @@ -102,34 +102,6 @@ public class SparkOpenorgsTest implements Serializable { "/eu/dnetlib/dhp/dedup/conf/org.curr.conf.json"))); } - @Disabled - @Test - public void copyOpenorgsTest() throws Exception { - - ArgumentApplicationParser parser = new ArgumentApplicationParser( - IOUtils - .toString( - SparkCopyOpenorgs.class - .getResourceAsStream( - "/eu/dnetlib/dhp/oa/dedup/copyOpenorgs_parameters.json"))); - parser - .parseArgument( - new String[] { - "-i", testGraphBasePath, - "-asi", testActionSetId, - "-w", testOutputBasePath, - "-np", "50" - }); - - new SparkCopyOpenorgs(parser, spark).run(isLookUpService); - - long orgs_deduprecord = jsc - .textFile(testOutputBasePath + "/" + testActionSetId + "/organization_deduprecord") - .count(); - - assertEquals(100, orgs_deduprecord); - } - @Test public void copyOpenorgsMergeRels() throws Exception { ArgumentApplicationParser parser = new ArgumentApplicationParser( @@ -155,9 +127,12 @@ public class SparkOpenorgsTest implements Serializable { .load(DedupUtility.createMergeRelPath(testOutputBasePath, testActionSetId, "organization")) .count(); - Dataset orgrels = spark.read().load(DedupUtility.createMergeRelPath(testOutputBasePath, testActionSetId, "organization")).as(Encoders.bean(Relation.class)); + Dataset orgrels = spark + .read() + .load(DedupUtility.createMergeRelPath(testOutputBasePath, testActionSetId, "organization")) + .as(Encoders.bean(Relation.class)); - for (Relation r: orgrels.toJavaRDD().collect()) + for (Relation r : orgrels.toJavaRDD().collect()) System.out.println("r = " + r.getSource() + "---" + r.getTarget() + "---" + r.getRelClass()); assertEquals(384, orgs_mergerel); diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java index f61ab779d..453efb433 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/raw/MigrateDbEntitiesApplication.java @@ -1,28 +1,7 @@ package eu.dnetlib.dhp.oa.graph.raw; -import static eu.dnetlib.dhp.schema.common.ModelConstants.DATASET_DEFAULT_RESULTTYPE; -import static eu.dnetlib.dhp.schema.common.ModelConstants.DATASOURCE_ORGANIZATION; -import static eu.dnetlib.dhp.schema.common.ModelConstants.DNET_PROVENANCE_ACTIONS; -import static eu.dnetlib.dhp.schema.common.ModelConstants.ENTITYREGISTRY_PROVENANCE_ACTION; -import static eu.dnetlib.dhp.schema.common.ModelConstants.HAS_PARTICIPANT; -import static eu.dnetlib.dhp.schema.common.ModelConstants.IS_PARTICIPANT; -import static eu.dnetlib.dhp.schema.common.ModelConstants.IS_PRODUCED_BY; -import static eu.dnetlib.dhp.schema.common.ModelConstants.IS_PROVIDED_BY; -import static eu.dnetlib.dhp.schema.common.ModelConstants.IS_RELATED_TO; -import static eu.dnetlib.dhp.schema.common.ModelConstants.ORP_DEFAULT_RESULTTYPE; -import static eu.dnetlib.dhp.schema.common.ModelConstants.OUTCOME; -import static eu.dnetlib.dhp.schema.common.ModelConstants.PARTICIPATION; -import static eu.dnetlib.dhp.schema.common.ModelConstants.PRODUCES; -import static eu.dnetlib.dhp.schema.common.ModelConstants.PROJECT_ORGANIZATION; -import static eu.dnetlib.dhp.schema.common.ModelConstants.PROVIDES; -import static eu.dnetlib.dhp.schema.common.ModelConstants.PROVISION; -import static eu.dnetlib.dhp.schema.common.ModelConstants.PUBLICATION_DEFAULT_RESULTTYPE; -import static eu.dnetlib.dhp.schema.common.ModelConstants.RELATIONSHIP; -import static eu.dnetlib.dhp.schema.common.ModelConstants.RESULT_PROJECT; -import static eu.dnetlib.dhp.schema.common.ModelConstants.RESULT_RESULT; -import static eu.dnetlib.dhp.schema.common.ModelConstants.SOFTWARE_DEFAULT_RESULTTYPE; -import static eu.dnetlib.dhp.schema.common.ModelConstants.USER_CLAIM; +import static eu.dnetlib.dhp.schema.common.ModelConstants.*; import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.asString; import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.createOpenaireId; import static eu.dnetlib.dhp.schema.oaf.OafMapperUtils.dataInfo; @@ -161,27 +140,24 @@ public class MigrateDbEntitiesApplication extends AbstractMigrationApplication i .execute( "queryProjectOrganization.sql", smdbe::processProjectOrganization, verifyNamespacePrefix); break; - case openorgs_dedup: //generates organization entities and relations for openorgs dedup + case openorgs_dedup: // generates organization entities and relations for openorgs dedup log.info("Processing Openorgs..."); smdbe .execute( "queryOpenOrgsForOrgsDedup.sql", smdbe::processOrganization, verifyNamespacePrefix); - log.info("Processing Openorgs Merge Rels..."); + log.info("Processing Openorgs Sim Rels..."); smdbe.execute("queryOpenOrgsSimilarityForOrgsDedup.sql", smdbe::processOrgOrgSimRels); - break; - case openorgs: //generates organization entities and relations for provision + case openorgs: // generates organization entities and relations for provision log.info("Processing Openorgs For Provision..."); smdbe .execute( "queryOpenOrgsForProvision.sql", smdbe::processOrganization, verifyNamespacePrefix); log.info("Processing Openorgs Merge Rels..."); - smdbe.execute("queryOpenOrgsSimilarityForProvision.sql", smdbe::processOrgOrgSimRels); - //TODO cambiare il mapping delle relazioni in modo che crei merges e isMergedIn - // TODO (specifico per questo caso, questa funzione di mapping verrà usata così com'è nel caso di openorgs dedup + smdbe.execute("queryOpenOrgsSimilarityForProvision.sql", smdbe::processOrgOrgMergeRels); break; case openaire_organizations: @@ -635,6 +611,41 @@ public class MigrateDbEntitiesApplication extends AbstractMigrationApplication i } } + public List processOrgOrgMergeRels(final ResultSet rs) { + try { + final DataInfo info = prepareDataInfo(rs); // TODO + + final String orgId1 = createOpenaireId(20, rs.getString("id1"), true); + final String orgId2 = createOpenaireId(20, rs.getString("id2"), true); + + final List collectedFrom = listKeyValues( + createOpenaireId(10, rs.getString("collectedfromid"), true), rs.getString("collectedfromname")); + + final Relation r1 = new Relation(); + r1.setRelType(ORG_ORG_RELTYPE); + r1.setSubRelType(ModelConstants.DEDUP); + r1.setRelClass(MERGES); + r1.setSource(orgId1); + r1.setTarget(orgId2); + r1.setCollectedfrom(collectedFrom); + r1.setDataInfo(info); + r1.setLastupdatetimestamp(lastUpdateTimestamp); + + final Relation r2 = new Relation(); + r2.setRelType(ORG_ORG_RELTYPE); + r2.setSubRelType(ModelConstants.DEDUP); + r2.setRelClass(IS_MERGED_IN); + r2.setSource(orgId2); + r2.setTarget(orgId1); + r2.setCollectedfrom(collectedFrom); + r2.setDataInfo(info); + r2.setLastupdatetimestamp(lastUpdateTimestamp); + return Arrays.asList(r1, r2); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + public List processOrgOrgSimRels(final ResultSet rs) { try { final DataInfo info = prepareDataInfo(rs); // TODO @@ -647,7 +658,7 @@ public class MigrateDbEntitiesApplication extends AbstractMigrationApplication i createOpenaireId(10, rs.getString("collectedfromid"), true), rs.getString("collectedfromname")); final Relation r1 = new Relation(); - r1.setRelType(ModelConstants.ORG_ORG_RELTYPE); + r1.setRelType(ORG_ORG_RELTYPE); r1.setSubRelType(ModelConstants.DEDUP); r1.setRelClass(relClass); r1.setSource(orgId1); diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsForOrgsDedup.sql b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsForOrgsDedup.sql index aa694c7df..7089a7477 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsForOrgsDedup.sql +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsForOrgsDedup.sql @@ -60,20 +60,22 @@ SELECT o.country || '@@@dnet:countries' AS country, 'sysimport:crosswalk:entityregistry@@@dnet:provenance_actions' AS provenanceaction, array_agg(DISTINCT i.otherid || '###' || i.type || '@@@dnet:pid_types') AS pid, - null AS eclegalbody, - null AS eclegalperson, - null AS ecnonprofit, - null AS ecresearchorganization, - null AS echighereducation, - null AS ecinternationalorganizationeurinterests, - null AS ecinternationalorganization, - null AS ecenterprise, - null AS ecsmevalidated, - null AS ecnutscode + (array_remove(array_cat(ARRAY[o.ec_legalbody], array_agg(od.ec_legalbody)), NULL))[1] AS eclegalbody, + (array_remove(array_cat(ARRAY[o.ec_legalperson], array_agg(od.ec_legalperson)), NULL))[1] AS eclegalperson, + (array_remove(array_cat(ARRAY[o.ec_nonprofit], array_agg(od.ec_nonprofit)), NULL))[1] AS ecnonprofit, + (array_remove(array_cat(ARRAY[o.ec_researchorganization], array_agg(od.ec_researchorganization)), NULL))[1] AS ecresearchorganization, + (array_remove(array_cat(ARRAY[o.ec_highereducation], array_agg(od.ec_highereducation)), NULL))[1] AS echighereducation, + (array_remove(array_cat(ARRAY[o.ec_internationalorganizationeurinterests], array_agg(od.ec_internationalorganizationeurinterests)), NULL))[1] AS ecinternationalorganizationeurinterests, + (array_remove(array_cat(ARRAY[o.ec_internationalorganization], array_agg(od.ec_internationalorganization)), NULL))[1] AS ecinternationalorganization, + (array_remove(array_cat(ARRAY[o.ec_enterprise], array_agg(od.ec_enterprise)), NULL))[1] AS ecenterprise, + (array_remove(array_cat(ARRAY[o.ec_smevalidated], array_agg(od.ec_smevalidated)), NULL))[1] AS ecsmevalidated, + (array_remove(array_cat(ARRAY[o.ec_nutscode], array_agg(od.ec_nutscode)), NULL))[1] AS ecnutscode FROM other_names n LEFT OUTER JOIN organizations o ON (n.id = o.id) LEFT OUTER JOIN urls u ON (u.id = o.id) LEFT OUTER JOIN other_ids i ON (i.id = o.id) + LEFT OUTER JOIN oa_duplicates d ON (o.id = d.local_id) + LEFT OUTER JOIN organizations od ON (d.oa_original_id = od.id) WHERE o.status = 'approved' GROUP BY diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsForProvision.sql b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsForProvision.sql index 6f5f93789..fb421f958 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsForProvision.sql +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsForProvision.sql @@ -15,22 +15,25 @@ SELECT 'OpenOrgs Database' AS collectedfromname, o.country || '@@@dnet:countries' AS country, 'sysimport:crosswalk:entityregistry@@@dnet:provenance_actions' AS provenanceaction, - array_agg(DISTINCT i.otherid || '###' || i.type || '@@@dnet:pid_types') AS pid, - null AS eclegalbody, - null AS eclegalperson, - null AS ecnonprofit, - null AS ecresearchorganization, - null AS echighereducation, - null AS ecinternationalorganizationeurinterests, - null AS ecinternationalorganization, - null AS ecenterprise, - null AS ecsmevalidated, - null AS ecnutscode + array_remove(array_cat(array_agg(DISTINCT i.otherid || '###' || i.type || '@@@dnet:pid_types'), array_agg(DISTINCT idup.otherid || '###' || idup.type || '@@@dnet:pid_types')), NULL) AS pid, + (array_remove(array_cat(ARRAY[o.ec_legalbody], array_agg(od.ec_legalbody)), NULL))[1] AS eclegalbody, + (array_remove(array_cat(ARRAY[o.ec_legalperson], array_agg(od.ec_legalperson)), NULL))[1] AS eclegalperson, + (array_remove(array_cat(ARRAY[o.ec_nonprofit], array_agg(od.ec_nonprofit)), NULL))[1] AS ecnonprofit, + (array_remove(array_cat(ARRAY[o.ec_researchorganization], array_agg(od.ec_researchorganization)), NULL))[1] AS ecresearchorganization, + (array_remove(array_cat(ARRAY[o.ec_highereducation], array_agg(od.ec_highereducation)), NULL))[1] AS echighereducation, + (array_remove(array_cat(ARRAY[o.ec_internationalorganizationeurinterests], array_agg(od.ec_internationalorganizationeurinterests)), NULL))[1] AS ecinternationalorganizationeurinterests, + (array_remove(array_cat(ARRAY[o.ec_internationalorganization], array_agg(od.ec_internationalorganization)), NULL))[1] AS ecinternationalorganization, + (array_remove(array_cat(ARRAY[o.ec_enterprise], array_agg(od.ec_enterprise)), NULL))[1] AS ecenterprise, + (array_remove(array_cat(ARRAY[o.ec_smevalidated], array_agg(od.ec_smevalidated)), NULL))[1] AS ecsmevalidated, + (array_remove(array_cat(ARRAY[o.ec_nutscode], array_agg(od.ec_nutscode)), NULL))[1] AS ecnutscode FROM organizations o LEFT OUTER JOIN acronyms a ON (a.id = o.id) LEFT OUTER JOIN urls u ON (u.id = o.id) LEFT OUTER JOIN other_ids i ON (i.id = o.id) LEFT OUTER JOIN other_names n ON (n.id = o.id) + LEFT OUTER JOIN oa_duplicates d ON (o.id = d.local_id AND d.reltype != 'is_different') + LEFT OUTER JOIN organizations od ON (d.oa_original_id = od.id) + LEFT OUTER JOIN other_ids idup ON (od.id = idup.id) WHERE o.status = 'approved' GROUP BY diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsSimilarityForProvision.sql b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsSimilarityForProvision.sql index db95cfe0b..abb812f6b 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsSimilarityForProvision.sql +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsSimilarityForProvision.sql @@ -7,6 +7,5 @@ SELECT false AS inferred, false AS deletedbyinference, 0.99 AS trust, - '' AS inferenceprovenance, - 'isSimilarTo' AS relclass + '' AS inferenceprovenance FROM oa_duplicates WHERE reltype = 'is_similar' OR reltype = 'suggested'; \ No newline at end of file diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizations.sql b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizations.sql index 73e39f8fa..42ba0cf91 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizations.sql +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOrganizations.sql @@ -50,7 +50,4 @@ GROUP BY o.trust, d.id, d.officialname, - o.country; - --- TODO modificare in modo da fare il merge dei pid di tutti i record mergiati (per gli openorgs, approvati) --- TODO invece per tutti gli altri con dei duplicati non fare questa cosa \ No newline at end of file + o.country; \ No newline at end of file From 0857100fb80ae9c88ffd2a16937fbf250965ed12 Mon Sep 17 00:00:00 2001 From: miconis Date: Wed, 7 Apr 2021 18:42:16 +0200 Subject: [PATCH 201/445] implementation of the tests for the openorgs integration in the openaire provision --- .../dhp/oa/dedup/SparkOpenorgsDedupTest.java | 2 +- ...t.java => SparkOpenorgsProvisionTest.java} | 173 +- ...39-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz | Bin ...39-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz | Bin ...39-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz | Bin ...9f-4ae6-4db9-919d-85ddc0a60f92-c000.txt.gz | Bin ...9f-4ae6-4db9-919d-85ddc0a60f92-c000.txt.gz | Bin .../openorgs/organization/organization.gz | Bin 71922 -> 0 bytes .../dhp/dedup/openorgs/provision/dataset | 0 .../dhp/dedup/openorgs/provision/datasource | 0 ...ba-1347-4e79-b455-19207c803791-c000.txt.gz | Bin 0 -> 3221 bytes ...ba-1347-4e79-b455-19207c803791-c000.txt.gz | Bin 0 -> 9393 bytes .../openorgs/provision/otherresearchproduct | 0 .../dhp/dedup/openorgs/provision/project | 0 .../dhp/dedup/openorgs/provision/publication | 0 .../openorgs/provision/relation/part-00000 | 2520 +++++++++++++++++ .../dhp/dedup/openorgs/provision/software | 0 .../dhp/dedup/openorgs/relation/relation.gz | Bin 22489 -> 0 bytes .../graph/sql/queryDatasourceOrganization.sql | 2 +- .../queryOpenOrgsSimilarityForProvision.sql | 5 +- .../oa/graph/sql/queryProjectOrganization.sql | 2 +- 21 files changed, 2664 insertions(+), 40 deletions(-) rename dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/{SparkOpenorgsTest.java => SparkOpenorgsProvisionTest.java} (50%) rename dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/{openorgs_dedup => openorgs/dedup}/organization/part-00000-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz (100%) rename dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/{openorgs_dedup => openorgs/dedup}/organization/part-00001-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz (100%) rename dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/{openorgs_dedup => openorgs/dedup}/organization/part-00002-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz (100%) rename dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/{openorgs_dedup => openorgs/dedup}/relation/part-00000-94553c9f-4ae6-4db9-919d-85ddc0a60f92-c000.txt.gz (100%) rename dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/{openorgs_dedup => openorgs/dedup}/relation/part-00003-94553c9f-4ae6-4db9-919d-85ddc0a60f92-c000.txt.gz (100%) delete mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/organization/organization.gz create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/provision/dataset create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/provision/datasource create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/provision/organization/part-00000-e55defba-1347-4e79-b455-19207c803791-c000.txt.gz create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/provision/organization/part-00002-e55defba-1347-4e79-b455-19207c803791-c000.txt.gz create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/provision/otherresearchproduct create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/provision/project create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/provision/publication create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/provision/relation/part-00000 create mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/provision/software delete mode 100644 dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/relation/relation.gz diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsDedupTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsDedupTest.java index f33eca57f..757ffcf05 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsDedupTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsDedupTest.java @@ -82,7 +82,7 @@ public class SparkOpenorgsDedupTest implements Serializable { public static void cleanUp() throws IOException, URISyntaxException { testGraphBasePath = Paths - .get(SparkDedupTest.class.getResource("/eu/dnetlib/dhp/dedup/openorgs_dedup").toURI()) + .get(SparkDedupTest.class.getResource("/eu/dnetlib/dhp/dedup/openorgs/dedup").toURI()) .toFile() .getAbsolutePath(); testOutputBasePath = createTempDirectory(SparkDedupTest.class.getSimpleName() + "-") diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsProvisionTest.java similarity index 50% rename from dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java rename to dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsProvisionTest.java index fa1fd63bf..9ff287c42 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsProvisionTest.java @@ -4,6 +4,7 @@ package eu.dnetlib.dhp.oa.dedup; import static java.nio.file.Files.createTempDirectory; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.mockito.Mockito.lenient; import java.io.File; @@ -11,11 +12,16 @@ import java.io.IOException; import java.io.Serializable; import java.net.URISyntaxException; import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.spark.SparkConf; +import org.apache.spark.api.java.JavaPairRDD; +import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; +import org.apache.spark.api.java.function.PairFunction; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Encoders; import org.apache.spark.sql.Row; @@ -33,9 +39,12 @@ import eu.dnetlib.dhp.application.ArgumentApplicationParser; import eu.dnetlib.dhp.schema.oaf.Relation; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException; import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService; +import eu.dnetlib.pace.util.MapDocumentUtil; +import scala.Tuple2; @ExtendWith(MockitoExtension.class) -public class SparkOpenorgsTest implements Serializable { +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class SparkOpenorgsProvisionTest implements Serializable { @Mock(serializable = true) ISLookUpService isLookUpService; @@ -55,13 +64,13 @@ public class SparkOpenorgsTest implements Serializable { public static void cleanUp() throws IOException, URISyntaxException { testGraphBasePath = Paths - .get(SparkOpenorgsTest.class.getResource("/eu/dnetlib/dhp/dedup/openorgs").toURI()) + .get(SparkOpenorgsProvisionTest.class.getResource("/eu/dnetlib/dhp/dedup/openorgs/provision").toURI()) .toFile() .getAbsolutePath(); - testOutputBasePath = createTempDirectory(SparkOpenorgsTest.class.getSimpleName() + "-") + testOutputBasePath = createTempDirectory(SparkOpenorgsProvisionTest.class.getSimpleName() + "-") .toAbsolutePath() .toString(); - testDedupGraphBasePath = createTempDirectory(SparkOpenorgsTest.class.getSimpleName() + "-") + testDedupGraphBasePath = createTempDirectory(SparkOpenorgsProvisionTest.class.getSimpleName() + "-") .toAbsolutePath() .toString(); @@ -103,7 +112,9 @@ public class SparkOpenorgsTest implements Serializable { } @Test - public void copyOpenorgsMergeRels() throws Exception { + @Order(1) + public void copyOpenorgsMergeRelTest() throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( IOUtils .toString( @@ -113,11 +124,14 @@ public class SparkOpenorgsTest implements Serializable { parser .parseArgument( new String[] { - "-i", testGraphBasePath, - "-asi", testActionSetId, - "-w", testOutputBasePath, - "-la", "lookupurl", - "-np", "50" + "-i", + testGraphBasePath, + "-asi", + testActionSetId, + "-la", + "lookupurl", + "-w", + testOutputBasePath }); new SparkCopyOpenorgsMergeRels(parser, spark).run(isLookUpService); @@ -127,48 +141,84 @@ public class SparkOpenorgsTest implements Serializable { .load(DedupUtility.createMergeRelPath(testOutputBasePath, testActionSetId, "organization")) .count(); - Dataset orgrels = spark - .read() - .load(DedupUtility.createMergeRelPath(testOutputBasePath, testActionSetId, "organization")) - .as(Encoders.bean(Relation.class)); - - for (Relation r : orgrels.toJavaRDD().collect()) - System.out.println("r = " + r.getSource() + "---" + r.getTarget() + "---" + r.getRelClass()); - - assertEquals(384, orgs_mergerel); - + assertEquals(140, orgs_mergerel); } @Test - public void copyOpenorgsSimRels() throws Exception { + @Order(2) + public void createOrgsDedupRecordTest() throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( IOUtils .toString( - SparkCopyOpenorgsSimRels.class + SparkCopyOpenorgsMergeRels.class .getResourceAsStream( - "/eu/dnetlib/dhp/oa/dedup/copyOpenorgsMergeRels_parameters.json"))); + "/eu/dnetlib/dhp/oa/dedup/copyOpenorgs_parameters.json"))); parser .parseArgument( new String[] { - "-i", testGraphBasePath, - "-asi", testActionSetId, - "-w", testOutputBasePath, - "-la", "lookupurl", - "-np", "50" + "-i", + testGraphBasePath, + "-asi", + testActionSetId, + "-w", + testOutputBasePath }); - new SparkCopyOpenorgsSimRels(parser, spark).run(isLookUpService); + new SparkCreateOrgsDedupRecord(parser, spark).run(isLookUpService); - long orgs_simrel = spark + long orgs_deduprecord = spark .read() - .textFile(testOutputBasePath + "/" + testActionSetId + "/organization_simrel") + .json(DedupUtility.createDedupRecordPath(testOutputBasePath, testActionSetId, "organization")) .count(); - assertEquals(73, orgs_simrel); + assertEquals(10, orgs_deduprecord); } @Test + @Order(3) + public void updateEntityTest() throws Exception { + + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkUpdateEntity.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/updateEntity_parameters.json"))); + parser + .parseArgument( + new String[] { + "-i", testGraphBasePath, "-w", testOutputBasePath, "-o", testDedupGraphBasePath + }); + + new SparkUpdateEntity(parser, spark).run(isLookUpService); + + long organizations = jsc.textFile(testDedupGraphBasePath + "/organization").count(); + + long mergedOrgs = spark + .read() + .load(testOutputBasePath + "/" + testActionSetId + "/organization_mergerel") + .as(Encoders.bean(Relation.class)) + .where("relClass=='merges'") + .javaRDD() + .map(Relation::getTarget) + .distinct() + .count(); + + assertEquals(80, organizations); + + long deletedOrgs = jsc + .textFile(testDedupGraphBasePath + "/organization") + .filter(this::isDeletedByInference) + .count(); + + assertEquals(mergedOrgs, deletedOrgs); + } + + @Test + @Order(4) public void copyRelationsNoOpenorgsTest() throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( IOUtils .toString( @@ -178,16 +228,63 @@ public class SparkOpenorgsTest implements Serializable { parser .parseArgument( new String[] { - "-i", testGraphBasePath, - "-w", testOutputBasePath, - "-o", testDedupGraphBasePath + "-i", testGraphBasePath, "-w", testOutputBasePath, "-o", testDedupGraphBasePath }); new SparkCopyRelationsNoOpenorgs(parser, spark).run(isLookUpService); long relations = jsc.textFile(testDedupGraphBasePath + "/relation").count(); - assertEquals(400, relations); + assertEquals(2380, relations); + } + + @Test + @Order(5) + public void propagateRelationsTest() throws Exception { + ArgumentApplicationParser parser = new ArgumentApplicationParser( + IOUtils + .toString( + SparkPropagateRelation.class + .getResourceAsStream( + "/eu/dnetlib/dhp/oa/dedup/propagateRelation_parameters.json"))); + parser + .parseArgument( + new String[] { + "-i", testGraphBasePath, "-w", testOutputBasePath, "-o", testDedupGraphBasePath + }); + + new SparkPropagateRelation(parser, spark).run(isLookUpService); + + long relations = jsc.textFile(testDedupGraphBasePath + "/relation").count(); + + assertEquals(2520, relations); + + // check deletedbyinference + final Dataset mergeRels = spark + .read() + .load(DedupUtility.createMergeRelPath(testOutputBasePath, "*", "*")) + .as(Encoders.bean(Relation.class)); + final JavaPairRDD mergedIds = mergeRels + .where("relClass == 'merges'") + .select(mergeRels.col("target")) + .distinct() + .toJavaRDD() + .mapToPair( + (PairFunction) r -> new Tuple2(r.getString(0), "d")); + + JavaRDD toCheck = jsc + .textFile(testDedupGraphBasePath + "/relation") + .mapToPair(json -> new Tuple2<>(MapDocumentUtil.getJPathString("$.source", json), json)) + .join(mergedIds) + .map(t -> t._2()._1()) + .mapToPair(json -> new Tuple2<>(MapDocumentUtil.getJPathString("$.target", json), json)) + .join(mergedIds) + .map(t -> t._2()._1()); + + long deletedbyinference = toCheck.filter(this::isDeletedByInference).count(); + long updated = toCheck.count(); + + assertEquals(updated, deletedbyinference); } @AfterAll @@ -196,4 +293,8 @@ public class SparkOpenorgsTest implements Serializable { FileUtils.deleteDirectory(new File(testDedupGraphBasePath)); } + public boolean isDeletedByInference(String s) { + return s.contains("\"deletedbyinference\":true"); + } + } diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/organization/part-00000-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/dedup/organization/part-00000-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz similarity index 100% rename from dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/organization/part-00000-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz rename to dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/dedup/organization/part-00000-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/organization/part-00001-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/dedup/organization/part-00001-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz similarity index 100% rename from dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/organization/part-00001-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz rename to dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/dedup/organization/part-00001-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/organization/part-00002-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/dedup/organization/part-00002-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz similarity index 100% rename from dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/organization/part-00002-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz rename to dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/dedup/organization/part-00002-5248a339-09c4-4aa5-83fe-4cc5405607ad-c000.txt.gz diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/relation/part-00000-94553c9f-4ae6-4db9-919d-85ddc0a60f92-c000.txt.gz b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/dedup/relation/part-00000-94553c9f-4ae6-4db9-919d-85ddc0a60f92-c000.txt.gz similarity index 100% rename from dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/relation/part-00000-94553c9f-4ae6-4db9-919d-85ddc0a60f92-c000.txt.gz rename to dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/dedup/relation/part-00000-94553c9f-4ae6-4db9-919d-85ddc0a60f92-c000.txt.gz diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/relation/part-00003-94553c9f-4ae6-4db9-919d-85ddc0a60f92-c000.txt.gz b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/dedup/relation/part-00003-94553c9f-4ae6-4db9-919d-85ddc0a60f92-c000.txt.gz similarity index 100% rename from dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs_dedup/relation/part-00003-94553c9f-4ae6-4db9-919d-85ddc0a60f92-c000.txt.gz rename to dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/dedup/relation/part-00003-94553c9f-4ae6-4db9-919d-85ddc0a60f92-c000.txt.gz diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/organization/organization.gz b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/organization/organization.gz deleted file mode 100644 index 45b0edeb252685bace9873a5810da3a49169093e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 71922 zcmY&gWmuF^w;ehqq)}iH=?KWJYFa`L{GcwHuA)(>Avt5;`2EC>G47Q z>?e=I&&nrLepjl;OMa-f+X^W~P1n&pk$uFyyY=5!U%VE|$JVr+>{LDu>yz{%c$O&i z_pZ`5WkB;PX}6v)*YGi`;b7<5v_b2khjq5h>(GqIFJhq5z<_l%_aGs1S2*{u@;b0< zV9o9^W>9fW!1pG3`1eMJ&?8fKN=Dx8od>sY|L38bj5EuWsbAqMk%xe1xwhn{5Nl97PtWE$EwyMXt~K}#yvQDYG&V=^8=MZc z+(yEQjv7kd=;D94$L>Ki^n>4dY}$P^X7?Z{<}bfH4Qo*kL0SpK{aAUSGFecr;8%NE zFYI%q8}D3M*rA2mSN;6icy)Arv*+>7U|;k7bWPEYb6w-!!`abFwtmzY(w6v(CBw$p znC$i@wX5=T$)>@pL953T?q>DU$M(|rRqV%8zfqK>ZVr9l&I#iL-vaFcvu%^oV!vZ_ zJ7EDU+I?1`p_-rCXO=J3B3*sA+V?MFeIHZ}^}1-%jOtgz&Uo|WH0QV7SXVvCuTf+l zmR{fgDkeIn*e(~o|7hRIKq0G8H7bj|JB9@B65zx@9lg03)?IiEwyP-@z z%}z_4WlPTzBkRS94!oqAPH{rkTbxb?KFnOdHVVHS9zM+9p2#ZpGt~E4DoZ}cxGgyI z_4c4mqnML)&RGrPu^S6EQNCR@wPrOxUTRoXZ`6zbdG^&+)9++6`n#Kai16dV=KUpQ z+L0Yfn_1Bq%Z^Oi=YWD|{#PBshW-UDweb=6D`q<5116r=yB^mxYEj=0&yXK79(P+P z2XVGi#lv{~hEg;9M*Cx(uP)C^q*WVNEv?1$@T0j0wEj5#lIZ;S!dV_dT=$gVlfyL& z!?NRN3CVBl337A<$TV>~59PVOK`KcqqzXB-Dl+tX^p^PZyD@w^j>vg|Dp?wHR!Dh# zRS}}!Gu05J$z_r9da3rdXcZ~^-sTMYHpaHcI%j{1TzxcnJ8QdH*8H+?wf~xUHh=Ne z_WdAnXW=R`F;Y}7=MF9#I&r-IV0i)iz`R-KwM$LZX_-6OeU+v8{(8N;{ew{G^c^l? z{CCM7E@g8QX~|!N)Sv1{4T_l!0s{SZskZs<@gHt454-!ECXf8zKpuMT{#}N%biSk4QUo%ZSopDK>7`S4gBoGk8+La zpycm;u@>k+bj@Zg{(fBT^LOHpZtPM2MZbnfp7i`EX@`TNAv>q?&Vc*{VI)YAIeWH+{SflpFT zjq^HV^KQQHHZjdP#+^nZMkKCOGBYH-m~TD2IcYc`JgU!|OC{gpHt#;?w`r{R-+1ES z-+FRk@}{Eiq!wLATlI})Z;blv=^5ij-WY*tMEi-6N8!YUPTL7qsd0sX)*>V(Y@_Yu z1l7x{zy)Pr(8rlJX0B6Dcyi!nuI2hg;$zy!q5Rk;_~U>p*g;njj=^g^wk8qwUL@fm zdx(s6{c5mgW4Uf|9PO8j8U`O;RDEZPjiPkf$wD0lC;kvGU~u@sFPMN&;|kKy1H zvl@N;@uni9elZL;qPWYDKj&TWJ?YBcrZry~;ZYkL7TVRauRMwCbtb9`siCrdyfXSm zEq7pifUYu$hRhYz8>f2KfbnWMOC3`PmEyU`$&u)l5&65v4~!RV7hzmFi~{JGnBOeN zso0>zYz?Uy`F0f+qtOja3bWVV6XV>Uiptyx_1$Y6Bb{yA^S?H=w?%fYtCt)!o(xeu zXn2bxY1{FlVD_S4*W$!{=G{qVJdt_rCq+mYH76HxyMP+Uo?QyJbn&|;;}=}o)po;i zZd3bW1_BuY&iwti{r#tUGI7~?Wr2k0Gp+ijjFe5zTygS@s4R#?utTFBfI|mjw2_IN z;OL>+lD`uc^tebYm)TFZz3h3p1#gV;V*nJP--N~~up;!s{plD}sY9&gvFB;h@(dHb z;#0G5^0kk4Gb-e60}{O15TX0BsbV2A5$=qZ??}_;{w4@}KPeKU_-Df64RE*O!*h)( zIW+Tf@}j7>1m0j=z};rT3grcFRYZVKs}LxZdk7ajRD1fRwH+!@xzaZArA(2eH-dri zq5+kC*T)~eVr`VsD~zrzP5sO+jacjJ({5VCn4JRXhEWeMqWdl1UH7;A1w1z8borOH zUy_Y=yM1>&S)+Z62^Yl5r#=LJY`)Dp$DolVsgYWAaZnAdM?3Dga`Z%FAYHzK5bSrm zY$z~r$a8+Ea&G^Lqi@Qbm*>Eg?tA#|_!+r)P}8SG%diJ(H!uA2Al;9S8zdAZ88j(im?`VimfLs-Su05?loDep@~( zMi)RN7c2cWg)ZQ1L4bfA5C*D@JE}mO)U>Xy1OMNx>q!2O^WGEp!_mXCO2gQHX@?4> zGAV`?U3oLAwq++me(@wt{`}Is-Qw0MlN{9GIqDz4VG6ZL8Zmi-+#4S-T}^XHD<2qe zV+!^2;%X`s4{KTv<>P>g+&XO3%K1mJwe?thA7@dr87&Vf%BF3uVlfWDK}4+%gqW(Z zqv9aKQq~_T0^(9kR!2s92C6Y%O-gSX>lkm{9PV$oH@|#+%_rxYYdMd9sW6dg(*A)uU$kJ0BL- zNp27P(We>~X7pEdoP=CzDx7chf0~M39|Scut7wr;QekKaXK;5@OckhCEGtPNYuO8L zV)1LURa(e)F*0D%Q<=6uV`c1V%;}e?fNK5A;R;v|GoccK95xcIfaP##`krW8W_}!d zE7XV$gwkKM)yS2%rOjMLflUf8jSyR4*5cL$}50U zYD1lx%cyYa+}MV!7bGU>)DwA&@E~{^@P0ov$p3L9ME$BGO)A|Pm7jnpR+=awxAsnq z?OzQv5(5|Q8;(2(Xh*P-7zpUISqFakzh~EJ-lN<_3xe8Tpb4#kMxq`dJCND}L3X^? z(6pQ-jELRdoP+pzN(2%`_7rO^cx+?{-<1}MScnx;!e7KDZ9A~!KF1q>r8!1LR8%sF zZ6X=sRd{5$8+o4Ex!|#H_|R2TbZ$sT9VcUNvoz`U!=2a4gW=X24wJr1@#bN&E$@*s5zf?`*ScRsu`k1y1mH7^r|7D zQJ+kK0z z*j)voq5mPI8>Q7v*esmCZAz*vZ!AF93Slkc%c!RgA1xz2T`SRYBn%F zqvcMqGrDX!+77?Pd7e(67^glJwyctl1FbAW4hwy?8gC9& zG1n2fdo=98M-Sk@jVO!QP>*H7BcbSuAwL1H(^qLrBXWw<$ z+9P6j(3%~OfnzB;b6)Vn`^{g;?2_F_SJn#}wbPCI%V~3pAzq8Qb4oP9Z7d6dhofZk z6FXaSIRkT;q5Q2`a2*TLAD3-AN>ejO;;UljpXON*6#bEH52C>e#psYMHL6Wg!vs&D z=dkG!gPvn&SwRuJ$&kP@nOQeHFOWkn>XcPCvvIfOI_qqsdRM+WR!*MU5>5Gf+JIko zMpbN=eR9{7(JQLaN7&E2V)M25%v!J$74iG>!~6K9*LK{B3rK&T*h@gpE=*V|@XqwY z)zl|E1LL1*i(te0pZ_9A_)L`HXCfyML4N~~@Uqb^C{=eewPSIkoWp7XQ{5Pk;hP4`5C9k zg}~SdM+6o60ST@@*BCr418fEngPfw?TKPWfEdPHUQ49{e zt-O?hHOCpka$6}^hsq>jf4I~4P?_*e@BUViIu0BUb0H>LcQ(Vr;#L+r?7WOJ+=}is zr7#o`3t<*Aw?G`)c_qTsoQUf}8&8b55|wnvJZcnFxL~E?{c3nrGM!Fvh%C65*f{X0 z8@Lymx{fH+VqH2x=2xP@u=gX^83mX!)d5lMEv26Honb6><3O zS_nM77>FUGza!Xpl9^FC_>*o5Uy4*Lxx8aM>}D_NN*oJ&L`HtToC!gIMTQ8$Q-!o# zOtLYz@nxGvQFb1BWsCtT3Nj)>HVh;n3jmM+*X9pE0wyYQKh+*LEv+G5OXAQ$@R6g= z&`w`peI4szY;XP;UnBNnqUku5J4GW5?Ekzd*CFDAEM*2n(`BPe5~O1$JUg-!ttK%M zAF>XJ_8oW1BA9f(P+9TK*p}j@>RSHl>%+R)STxort0}o_7)853FUV;AJF<2z^r~Lk<@R5d{@vHr`gy%JuD61xihakvvU= zi|tqx116esW0$RXzlWn5G5A5^kvB$%E%RK8T9$vbGRx{dq}MiLo?1oTyLx@MZ)tF4 z=*h*`s-L17XYp(POTsT)M#MHt`5B#fACy^F(o$sujfgh~OW5+Ic6{@>(#!I)V)9UtxeEn zX09dFgS9t%=GxZ{sT8}b!eeq;c>($YSDk{weu%T1-@Rf`zwgxs->(;W{r0(%e(?jE z={vO}>Eq(XM0V?ng-E(u3xVJ7HeFLdWXP#XR%=W7`yEQ~i6g%z#EmH~0Y1QA1QtcO z01!nHD0CPSg8~m10HQcG>wr@2JLEX%E!nBxP%Oy&Tj?I5md9U1IwN zI)~f?O}&GjCsFV>Qb zr*@=PmHLm=uGO;wBKvuvndP&)>+Q6u`#zO4R{ekD-{=27{`t4xlo(h)yeo+dd5<5x z+lS>{y5Sx5#N#KDA+E6eYKXB?o3DC>!Hwsn?FXD^waYkRNP70d`8fRgY^3zLM3f0M z8lw_Y7S!M9f=a$o*^AafKU6nq*tdxn6YJry@c6H<1lqR&GQ!8_hq^GD>zpvMi8bJPcv$3B)ViUL~)Wy6J%8`jTF7!JNot}U?>)=l3b^LDKV zwCIvd%!scDh2oNK(deT{H9_VdbQ8MD(vJb=1VeD3@TTeY@Qm zguQ$F`nYFG`f}hnjd)S7L~~<)EL(x))2fJmCagsH*NZHGtXC`ed1dO*tXJ1r={`0N z%P}Ivelo7jr&(&0rESJ720Kf0X8rQSVo0y(tZd)d<+7PX)Ng(`J6o%+uUBHb!Te;P zRrIP8&&5mQrc$>w$@dl95VnJ~|JJ2RU`xtYkaK>7{0|F;zEX`v4)yP6&qAe-1l-AC zjr!;gG-?6tbPAD^Wsf6oFsSnRovNq$c@k~W?qWR%&NPlBxBeH78S8q;&|YNT`Gxv} z$88`vXboOI#?(gn3#{!u{Zo)CX0YYQy-04-CCQD*Ip*%F1Lbndv zwW>aa(slt6$whHrgBc37;;EmM#l*9|8b{d)f?=xhEr6-c18y*(9);NBv@j4TXB&o| z)>w--D#LHJW$L5IlFOAf9ZtjoORriSJ&<00vf}P_vRkQ%s);lXLS+fniiVm3(&l+_ z`vX`_3lst3Nvk+N=l`;kS0piG^|-sZ;Ja==6axJysZHXOcZNBn_1mR|J zK@j}Ef}#UKhEO%6DyG=_2WQ+K+Hfh*)S)U*Ltj$@p|2I2Rm4 z7$vJtXHud2&elJVXNt5q1;$L3*j9=)nY$n9EfuHi&=`=%yG+$hmhHxAl0FP5S~YdftI|QzqAbCw4kR#BVFyq4V7@_r7vd4@b8~mxSI)aU z*0N~xv~;v_j7)I0pZ~BMj@mr59d5-RhENgO0p{^&x^NjuFx`(pDUjoC=!(gsjLe|R zt*)#HT%nh$`$&Nohr^`(uSEGWdDA6QM2Upl7TUKA(XIuPWR(PLUKH0oZ0+Wpr&Cow zo01DZz(GNga1+iC;+LF~=}dD{(`-i}`-C}SFG_N1YX3YNV?+`*ZfAR05Cr`db4K|h zAVHL!A~nrFyT|W+d3L^#aC8%S(CzXaU$)ItIwV$}BT330`;Z$FJ|1(cfLdp0vdV#&)zej8beY?`bSF5>7Ctcpe-3sU?p)jsT2|I)b_bGV0OlmEkYbR02h! z{hgC7!9RW_DKZvYS_sLEPaSfI$@rnlWzxSHWxlT)wF*o@bh?<}AQy=K3#Aa>MnPRb ziFWU_P-0=QE&2ANyRuW{sN71YZm6Iuc=sSir!&0ZN!$+Sg+C3rR$^o`#3DcdPk^rY zhTWeiHQkaoS8r831xCJ`4b3I&8}`1@NW>es`%1tzo*~=-Wgs#W1_&IMdm5CU>0Kq;Zyga8igUlp5ACp_ciYhWPIuy4 z*>3Uot{e)d5+>^By4;tlGhinE)$R+RKv8j3)Y^UW!T?W{?)PhA^J#0lt}InQpM6B~ z^t6=$o*0f8tiD>Jf~C%@hb`Wcmb7-)luMoPc-?^t?n@{N`oZ8#^u@wpgVVP!gPQR_ z_#Dd-Xy-i#8(*tf(b&6T9Sn-XZjIa4bDhqAf0Brt)bgXaZLQ;8^z{7v8C$*(bh_z1 zCCIbY=X|p7+PHeybdatU(pN0Vt>0zYCS5ro?Y0gvccXU6!nUtrj1q}_j zVlle>oA0v^*H6_)^KnGLl#U5K^p(ePdr>~DAb}^doolAUYg%|_fX?XiMZ^Hg`T=Pj zGzsu@2#U!Gya#9!mt2e3CQIkeXi~&?L=Q0Ja>aTF2J@QzhGC7)zO#3Ut4*nsSQq?8 zw|Pj)q4;mCj*M(CybxN%jCm~*;$KDNRC?Gn8r8kRmmJs=`gGom5wk)`cRzk^a4>dN zNOVJXq5l_5x(5x=+{&nB!qnzV{cpb#*N8Q0FU`WK;4c^Vq?~N0nz&O@_mNH$k_#~z z5lpht%Sw9B+{SA!kuMivWMYVy9VmkT{sgso-Ydw<4%ET*naISjx)%6@^0xa|V0>!7 ze^uK;(~vrpqo}&I;c}d-e|2?D^K4#?b2;UMged+9lBSwS@JXefXr7XFR7zfDwI0i} zMYUeYohN+DfyQ0^2fp-fwuAYO?t%9jZmPpekZ$C^cEX!a8vZI)ocYpM$E7E{QTu)R zSB&XZ^de`Qh+n4gKmO1U&^p5uf9(K&_#om3%HG5JjOO;?-IW6RmMy`~s{2Z>=Tut9 zlI}xQ72yfWHEi@;$36k0r#U;>slLN3*ZzbV7ogo8@Omgj{XL$3dD(8y*=4Xj^Au+z zF>%ZdKDv6yo|ghoLb(IYy?p81`*dKt^5NQ=_hSFK)omybAH@5XU2q!Bn_;-MF<&(C zx5bO#ujzkBwVvf*Lxdm{FgOEg25`n7yA^0FaNZE0tvuM+Oa!Ddi{liWt*2U(_ z-|crduUizi{#TyqXG%DQW-Y%FmjJ#lFcfaiduwXLv5^uw{SDl5*X)W7- z0g~N$c@YtLu@5hxDo-a`4ysg!Dv_|!5Tk>JxPuljxvj}9HF`4^fPcX`o&|S16V2GI ze?euc9%_gx!5d*;<%EiCVzX$95w*mLyc1>gNfzF?C=j`d|ew7oI zg@1)`*8_rcgmM+DKR7g2oJ1&G8|k_oVP5P(I??BhMW--2(>mo|p*y+Tz(eK!Ue~sY zmW#?dYT^0MJVExnz8>8U5wH8Q!w>CeTc=d-?Fy+7Dr+dl0@EsMeECue@jn_c<`B62 z!jG_2=*^VFnhDzb7n>PaY%ZW)AU2gKGaxow!UFsvGiy4;NZ>0lxC#51Ws6gj`v=dA zJWd3cE+hHvFUzfJR)b$ZXAOfcZh`~HPPYo&3@*15IU~+Vn$c_l>1z1@NmsUaKCf9P zF{&}LdFZ)f{j>WaN>6RSks!GKL=x#*Qn~(24>5|iy0uN;Noc0}YgcFje^x_r8k%-S zRL2-!^qv~2{H2?ya#!Bx__aKw`+x@}#$iJslz}JE6aHeKfu3LK7DA!;JJCRST`8pJl7SCS{P+_JHtw^i}82Uk&mt-va1F;|y`~mVbST z38M4*{utT$e(p^bjGblZ&&!J_$xsceG@~AhQERgczUNS++Q&=j0+uGgigeaMp>PQ;dT6;&(V*77a$h)HhR?i^cofTXmRaJrF7 z?1b~-8cGy(83n1@Ey(3)Okax5Kyywe)fC!qjC^1bY`X#Vxk5;52}JO`?FJ~2x?V0Q zJ88|$cz-q{4^U3E>WvXL;Z9)`Q$NBc00KE@buJC54?dF8T!$JzP#=8-lKN+_H>q5I zkgQ5-fqB(MGgvFN!8zXR%=j}wV_k2?OEvpXC<-tVUVF#wd?W^g31wF|tamQQMjmCB zsz#`v+@r2~1`c%{_?Pfc|7HG5U@?IE=q~{> zUmEcxuo&onswKN{Ix%z;oAK8E58<$X*zI5Yi%=0h7|s-2Pr#@3vTU{d8kkdeATM9t zJP25QpFAkucN58MEh^?S+V&ubq=cq#-&-+B$Nf}3B+o1=?4zk%VAZ-68=AID z-(&{1W-WmzSnnGPb4k{z-p-)(C2lx4@$;&=lNx&nV_Wrm^(ZPDitwBl+HwEQaPLJ2 z$0u*rB+9Say;1CO8o~5-NbHT-P@wq2f(z{c!3Byhn4d*Mp&cN&pM2)X0J2v?yKYYjL04QS#3+_rE)`Uuo}=nJ$oQq7~?D!u+011|D~@jB_EnzjoLBj+fu67HCaj#3%gdNDr+0@MT2f%a1_h zjUv(a9v)45qmm?Ch1^e)S;mgfEH-;>e`Y{2E&NU3k%30^ng5Bjj0)dM8YZ8ownKK{ zQG7zKO8?qdRW|52I&vJKoJ80%0p)}c2nPsSJDZ+;|19n;H?f;eq9trBOXimdSa4x5 z`TjXP7RaOP4Nn7ofugy=sa-p50UkdJacd*7rrEYd8-_yEm5{H&ZJ$p#+SF7mucouh z>XMB7yi1HdwZjXLYh`~(PxuzCqa#R2tl zfb3=G@k^hY-4|~cBYxKERsD2T0d_H<>YkS}4s5mzYDN}o4Vk3~W9xmRCPHI;iR+H6 zF6`2j@dRn*reGa^JK6m3AXD5CIK|AZhqw)NE7VbGn zu2mG8$qjpjj-!%P1Z$?4I&=*BMsb#u#5FptpN*vcRjALf3Pm+N0xA?4hzaWL-PPXt z_0+e&BV+vR0y+aUwho4K=%luz>YSOVN^yW{7QxK~^L+-BG3I`wdyLtD%Z-IYqn2yu z?to@%U}Y6l1~s0La2%kZj==S>nzx0y{z&KVMHiTn)w!j{b}o$gF81AB;Odt}H zG0m2a9I;@O5YS@TJc)f=dHqJOm&KSy9}V*p=Li)ww$0`rnQDXX#|S&hNz z$~55`bXI0!iX>yMdf;ZG3!Wba)#bY5SbY{#BzW6xis~~Y=BNFsud`!X-pqv?X$;#z z&wu;UB)cVcJ6^TdgPO!#Oes%xNNgi;BSC4U?aas%2IgDO0GMYJ{N@Y5{C3+J&}cU| zV$0d@a~f>2r7$jkq%TB|;AN47jCA}x1xc6_He#N5>an{^tVc)gmUwW;D8?{5IIpuy z!JTz(`MSxMY3qi2)SO_d^HiNr>Izsa8sXNkza7iG%T+Zccg=NP>P=5 zppDy+M}W(LN+JQn1RU;w(!dl$2AJSI4#zjC>8)rA;YZRZLP9OE?bmt5DYsumOEERS zi2gAG6d*L{OB*0xoqS(8g;E}|rieV!^?6$NBU;n0rzXL$~kN%Z-b)`gns5E7+X`V}R zU!jEkSLsrJ;Z_P*bp3@vbeFM@%ppApe*VHBx>jj*WHe`+WoQdRzAmS1O}0{4OLL)7 zY!#H#NaZnRlnz>=s&I~#amVZafGyDZppKGI%cZ*9k5%ALc=*iq)HW}1(#sGoET98ca^S)MtZaP;(mw_T3n2`^ zO1G*_H=g63q2Y}TSOOOh4{p-~#s7_Zmn;IBH@eR=>f#@Wd1 zPxpbK!v^ktfpA%KfBM>C!N}$kCk)}rSD5Ci;VPZo{Kc_6rvWKZ5?4$fz03{JPgcHW zEPIWkduPaWwq7pZg8tKtva|(wjT9oopVwnsx}Mir&9uo|UB>&bErh0k|HNb18<-|O zpU>6PlM6)mS?cuzW=uG69Yx_pgXm3ci>N=xPu=3W%{70(7S~7^8jgI-f;6Z5mpAx) z*>ZFub;00PJp6h7W)OFg`_Bm6;as$Khr%Ieo^n@lM8c%BzK59QaQtb2rHmYq{Qe2M z?n|I~FQOa*VfRSL-H`m<;a=APq;*SpL1rV8wTS|mOO_rBAfZh#8^P2>maQoa%{+Qt zI)%xx2DJ2TUTgT5dP!n<9~b;ib>gj_jLfU3z2cnSki`%JE|U;_9hbyDH^hcL^IAI1 zXM-LC-@z$dtKX=2urvg5Oye&gJ!^Hmn7r$_~(=aX|u6|x$3 z(LYqK21Df&K|3G=3(3_0D%b6Nk1{3DBh?S`WAS%+4NYMf=v*Zu za7eS!R$Xc6YV1V}FTSnJcXQn9B9_Lp_dnex#z#f}XGO(_Sy7P!;ei#E4hJ7tQQgi~ zvE%}(;`&7bGDTo4z<-=#@hNV{^*q`Xcjb}h+mg8mO@?*^GdFxJD!sd#PeJk8&R35;*lDhIj-9m9K zNyf6rH6#y9tG4yuegFC!od^2-pLK`_W*x#pMFD`vUW5l&hg$eNTpwOtdpY^ihRiWT zh7z~uy%dLb)^xO5DVAAbXi>No47L<40`v;AXW5fBE30kSx5UOl)JAoV*m!YC?8L0P z2e^mhD5m%f^T6uo^NWbKAN4ISUAbYeQ{uxPKR&7Uvrs-RBhOoQ%4tha%36!s(p0k- zAKIl~=Dp}G@WbtX=gB4rE2T!BFf$RkJYbzVK<@&jH2%#l)&{?yH*I1{c|FBqj*1ri zBElhzDF8kZz!a<%&%QNQ6}Z}4L5BosNFL>yrdtzR%A}jf>gz<@sf^v~O8~hE*y?AM+A3%oS z31sH5@L!Obe%06w9gpck^|Ae|EjQ`Um2!KipW|SC1N0&YF5sx6&GW3vBF*=UB0IG) zQH|zIETW#^kahuSo1OuCa$dx?N?oQey3xBN+!wT()OM>pwahgM3jx^ zF(OI+>n06i9C9Tb&#~Qi<=`50ksI|g*b7oPH#hYsnWGo;V>d@i(a5idXx~~aS9D=; z0kILYflH|}GcLwJSwCTr?E^jr6N8Qe5i?<=D1tJhg%J%w5V9Bos2N;1q@ZiJR1)SenTdqRo46mT8fbLF`dGK`U1-+$=^+1LbiAL^0}`PNr%W_G(($2u;wbW3B3F`E8bgS zGWos~bu&mdV5=b`zIxmT$S=q&)H~^@CM2GT3w+Ox%NvgJM1aEM{Cj;+*f1OpfephV zh*0qQ%%juE((mZr(vfo_U-tkz);h`wAm+B34g7M-!uwWT&c^c7s$-p>(uv*NzJH}# zM#$abv3PR>kXg3=;$1Ogdif@Ii{AL5M$Og}X=PuP_umqXt)Y^2vbZ_&22UfABn4%^ zrc_}h8hoTEPrljtqdyUwZ__maZ@~3?1&$T43IqBExu{d=yzQSxQ)haW$(p#^|3G=( z9ldb>M=Bh_NQFjJ15jdDB#&UE!lZs9*}>Vmt<`Fe@u`y?_MR8(E8|oQf4o+D0C^#_ z{;FWYloJ+7jdx)G7`YnO*=yLZf77-@^SUz#ED1a$?+ORT+`J4EoJ;wF-(|0DF~IwW zo>O)_P8maX55!mWqW}w^-}j@z{k-;jp&AA4w`l1)QZQRh<$(OYPh!4A+9cqXpN5hMl`dZVT>Zxg1tUAZZk{F0aQFi?!#-d=vm`V6C z7@CO1qX2@bsabK;I>L@V{Do=PyywfNg=&`@)!Pu! zycJoa!p3IgXC*m>{&%577R7jvj&EEPh7d^F6b@tM<4mcmmQ^$Y5)>r0M`L)&vQYsX z_{VV{!8mSU6apOgiq8>je}LEH0^?Vrak%*|iodWBEh=r(4(Q zPmh}V>Ai9E&S%S+{;T`l@i!KbYV9ww~9sJ-!#4kVjkWhnkbz+b|Y-z0(cN%5ma0&vDtB*ATYOaI_{%R z^>d{V4!q&W6Y8TbFu_3WO}JT%sOfW?_Yd-1&BT8YAQK1Eg3{g`@Ax}79DoYC)1d1X zIH9N-j?Zc@6vocXmQ^HJio#9xA(Z3jP(XzIms8Ddw}))JEIm|vZwKvJ@2eeLYt+{O zmUREHGObHq{a`YdO!RQCM0fRFB5G!&HcZqeONQClB;!JSE| zk(`4IIzB91Hm=`r@Wivx7~T48?}<<6BW10MbAB~DQp+5|YiJlGQo@Hq#Q(wgP{17c zQ?|m4HkfdsfZH?|w+%zO2iDM2x_mslpSyW}c1YQA=rHgC@>vN=rvx~{a=(Y^yGF6| z&DZeOaNp<=WnPfrnZTj;5(NHn9+eKWS@m3?DQn=3NN{P0@{K564dr8l07v#fEuKGS z)*EHqAkLBaKtxT4GJWc|U)13JA^&mx_=Cd$Fb6vf1kNAO7*-18D$d zbu7U0;V^l%c2~#H4*AS8XpCa_j7pz~b>T)K%oOEKZ`{`?g1{(hVtUOtt557$;@VEb zZuHF8=R2v>`MJhdAT+Z;&`FbjPessH-|KF`(5H8!NGbMZ@W|-ZH&pq5kn$T0QqsWV zfy>N>`wc+K=x9H051{9KdTZ_q=1*E6LIB7HbI`xea_z*fW>k&QKi+ZmCQ=-o0fu$E zTE%Fg|23=&-8(A$5b7=%WfJ>wz(iQ^F0FM_ci(;gF@myWXsh!k^s^YL6-H522==-1 zBki`$C4+{W!3`0`qsFU*;8B;f??&jI1DGz=Z;95X1uDTtO5f(!L<(W=D-mj?nx*Q1 z^P52Wc>`8;VMFQw&oDk|k)~UA#d*ml7CKVP&gb+^s8umK!^CPO;RT_bO00$dc0800 z0WuxO*a3dqOamo5K*e=E++v{$`84NgrZ%tKLnlgBE zY;Nun?+WFkGZMdbK&$Hu?UjD=oq@V3UqY(Ok}iiai4MVJq$AYmU$(TtvIPw9;DUxd zX#?4E>-KBYm3X8_pGwD%LQ}wN;OsZjJ z&3YkDXr

$p)H|Z>W9Cnu4MbUWh=|rt|f1y=2 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

h+M&p)GUe{?{P0w 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 HWpe=lUNcFT literal 0 HcmV?d00001 diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/provision/organization/part-00002-e55defba-1347-4e79-b455-19207c803791-c000.txt.gz b/dhp-workflows/dhp-dedup-openaire/src/test/resources/eu/dnetlib/dhp/dedup/openorgs/provision/organization/part-00002-e55defba-1347-4e79-b455-19207c803791-c000.txt.gz new file mode 100644 index 0000000000000000000000000000000000000000..a54e8401c7d16c4cb9a11ff3f3ad0ac0f073772c GIT binary patch literal 9393 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 diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryDatasourceOrganization.sql b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryDatasourceOrganization.sql index 687377aa4..f72e72105 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryDatasourceOrganization.sql +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryDatasourceOrganization.sql @@ -13,4 +13,4 @@ SELECT d.provenanceaction || '@@@dnet:provenanceActions' AS provenanceaction FROM dsm_datasource_organization dor LEFT OUTER JOIN dsm_datasources d ON (dor.datasource = d.id) - LEFT OUTER JOIN dsm_datasources dc ON (dc.id = d.collectedfrom) + LEFT OUTER JOIN dsm_datasources dc ON (dc.id = d.collectedfrom); \ No newline at end of file diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsSimilarityForProvision.sql b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsSimilarityForProvision.sql index abb812f6b..92462d0cd 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsSimilarityForProvision.sql +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsSimilarityForProvision.sql @@ -8,4 +8,7 @@ SELECT false AS deletedbyinference, 0.99 AS trust, '' AS inferenceprovenance -FROM oa_duplicates WHERE reltype = 'is_similar' OR reltype = 'suggested'; \ No newline at end of file +FROM + oa_duplicates +WHERE + reltype = 'is_similar' OR reltype = 'suggested'; \ No newline at end of file diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryProjectOrganization.sql b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryProjectOrganization.sql index 13cfca871..bcdef8221 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryProjectOrganization.sql +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryProjectOrganization.sql @@ -16,4 +16,4 @@ SELECT FROM project_organization po LEFT OUTER JOIN projects p ON (p.id = po.project) - LEFT OUTER JOIN dsm_datasources dc ON (dc.id = p.collectedfrom) + LEFT OUTER JOIN dsm_datasources dc ON (dc.id = p.collectedfrom); \ No newline at end of file From 11b22b2d23fa6a96a568a316434509b9c1be0f66 Mon Sep 17 00:00:00 2001 From: miconis Date: Thu, 8 Apr 2021 11:51:47 +0200 Subject: [PATCH 202/445] bug fix in the query, it now exports only relations with non-hidden organizations --- .../sql/queryOpenOrgsSimilarityForProvision.sql | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsSimilarityForProvision.sql b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsSimilarityForProvision.sql index 92462d0cd..e127e6785 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsSimilarityForProvision.sql +++ b/dhp-workflows/dhp-graph-mapper/src/main/resources/eu/dnetlib/dhp/oa/graph/sql/queryOpenOrgsSimilarityForProvision.sql @@ -1,7 +1,7 @@ -- relations approved by the user and suggested by the dedup SELECT - local_id AS id1, - oa_original_id AS id2, + d.local_id AS id1, + d.oa_original_id AS id2, 'openaire____::openorgs' AS collectedfromid, 'OpenOrgs Database' AS collectedfromname, false AS inferred, @@ -9,6 +9,10 @@ SELECT 0.99 AS trust, '' AS inferenceprovenance FROM - oa_duplicates + oa_duplicates d + +LEFT OUTER JOIN + organizations o ON (d.local_id = o.id) + WHERE - reltype = 'is_similar' OR reltype = 'suggested'; \ No newline at end of file + (d.reltype = 'is_similar' OR d.reltype = 'suggested') AND (o.status != 'hidden'); \ No newline at end of file From 34df35926cebae09f303538702b607097aa6ccb5 Mon Sep 17 00:00:00 2001 From: Andreas Czerniak Date: Fri, 9 Apr 2021 14:35:30 +0200 Subject: [PATCH 203/445] add xslt, personname cleaner --- .../transformation/xslt/PersonCleaner.java | 206 ++++++++++++ .../xslt/XSLTTransformationFunction.java | 1 + .../transformation/xslt/utils/Capitalize.java | 14 + .../xslt/utils/DotAbbreviations.java | 12 + .../transformation/TransformationJobTest.java | 25 +- .../dnetlib/dhp/transform/input_omicsdi.xml | 60 ++++ .../scripts/xslt_cleaning_REST_OmicsDI.xsl | 297 ++++++++++++++++++ 7 files changed, 606 insertions(+), 9 deletions(-) create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/PersonCleaner.java create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/utils/Capitalize.java create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/utils/DotAbbreviations.java create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input_omicsdi.xml create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_REST_OmicsDI.xsl diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/PersonCleaner.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/PersonCleaner.java new file mode 100644 index 000000000..069060722 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/PersonCleaner.java @@ -0,0 +1,206 @@ + +package eu.dnetlib.dhp.transformation.xslt; + +import static eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction.QNAME_BASE_URI; + +import java.io.Serializable; +// import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.text.Normalizer; +import java.util.List; +import java.util.Set; + +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.hash.Hashing; + +import eu.dnetlib.dhp.transformation.xslt.utils.Capitalize; +import eu.dnetlib.dhp.transformation.xslt.utils.DotAbbreviations; +import net.sf.saxon.s9api.ExtensionFunction; +import net.sf.saxon.s9api.ItemType; +import net.sf.saxon.s9api.OccurrenceIndicator; +import net.sf.saxon.s9api.QName; +import net.sf.saxon.s9api.SaxonApiException; +import net.sf.saxon.s9api.SequenceType; +import net.sf.saxon.s9api.XdmValue; + +//import eu.dnetlib.pace.clustering.NGramUtils; +//import eu.dnetlib.pace.util.Capitalise; +//import eu.dnetlib.pace.util.DotAbbreviations; + +public class PersonCleaner implements ExtensionFunction, Serializable { + /** + * + */ + private static final long serialVersionUID = 1L; + private List firstname = Lists.newArrayList(); + private List surname = Lists.newArrayList(); + private List fullname = Lists.newArrayList(); + + private static Set particles = null; + + public PersonCleaner() { + + } + + public String normalize(String s) { + s = Normalizer.normalize(s, Normalizer.Form.NFD); // was NFD + s = s.replaceAll("\\(.+\\)", ""); + s = s.replaceAll("\\[.+\\]", ""); + s = s.replaceAll("\\{.+\\}", ""); + s = s.replaceAll("\\s+-\\s+", "-"); + +// s = s.replaceAll("[\\W&&[^,-]]", " "); + +// System.out.println("class Person: s: " + s); + +// s = s.replaceAll("[\\p{InCombiningDiacriticalMarks}&&[^,-]]", " "); + s = s.replaceAll("[\\p{Punct}&&[^-,]]", " "); + s = s.replace("\\d", " "); + s = s.replace("\\n", " "); + s = s.replace("\\.", " "); + s = s.replaceAll("\\s+", " "); + + if (s.contains(",")) { + // System.out.println("class Person: s: " + s); + + String[] arr = s.split(","); + if (arr.length == 1) { + + fullname = splitTerms(arr[0]); + } else if (arr.length > 1) { + surname = splitTerms(arr[0]); + firstname = splitTermsFirstName(arr[1]); +// System.out.println("class Person: surname: " + surname); +// System.out.println("class Person: firstname: " + firstname); + + fullname.addAll(surname); + fullname.addAll(firstname); + } + } else { + fullname = splitTerms(s); + + int lastInitialPosition = fullname.size(); + boolean hasSurnameInUpperCase = false; + + for (int i = 0; i < fullname.size(); i++) { + String term = fullname.get(i); + if (term.length() == 1) { + lastInitialPosition = i; + } else if (term.equals(term.toUpperCase())) { + hasSurnameInUpperCase = true; + } + } + if (lastInitialPosition < fullname.size() - 1) { // Case: Michele G. Artini + firstname = fullname.subList(0, lastInitialPosition + 1); + System.out.println("name: " + firstname); + surname = fullname.subList(lastInitialPosition + 1, fullname.size()); + } else if (hasSurnameInUpperCase) { // Case: Michele ARTINI + for (String term : fullname) { + if (term.length() > 1 && term.equals(term.toUpperCase())) { + surname.add(term); + } else { + firstname.add(term); + } + } + } else if (lastInitialPosition == fullname.size()) { + surname = fullname.subList(lastInitialPosition - 1, fullname.size()); + firstname = fullname.subList(0, lastInitialPosition - 1); + } + + } + return null; + } + + private List splitTermsFirstName(String s) { + List list = Lists.newArrayList(); + for (String part : Splitter.on(" ").omitEmptyStrings().split(s)) { + if (s.trim().matches("\\p{Lu}{2,3}")) { + String[] parts = s.trim().split("(?=\\p{Lu})"); // (Unicode UpperCase) + for (String p : parts) { + if (p.length() > 0) + list.add(p); + } + } else { + list.add(part); + } + + } + return list; + } + + private List splitTerms(String s) { + if (particles == null) { + // particles = NGramUtils.loadFromClasspath("/eu/dnetlib/pace/config/name_particles.txt"); + } + + List list = Lists.newArrayList(); + for (String part : Splitter.on(" ").omitEmptyStrings().split(s)) { + // if (!particles.contains(part.toLowerCase())) { + list.add(part); + + // } + } + return list; + } + + public List getFirstname() { + return firstname; + } + + public List getSurname() { + return surname; + } + + public List getFullname() { + return fullname; + } + + public String hash() { + return Hashing.murmur3_128().hashString(getNormalisedFullname(), StandardCharsets.UTF_8).toString(); + } + + public String getNormalisedFullname() { + return isAccurate() ? Joiner.on(" ").join(getSurname()) + ", " + Joiner.on(" ").join(getNameWithAbbreviations()) + : Joiner.on(" ").join(fullname); + // return isAccurate() ? + // Joiner.on(" ").join(getCapitalSurname()) + ", " + Joiner.on(" ").join(getNameWithAbbreviations()) : + // Joiner.on(" ").join(fullname); + } + + public List getCapitalSurname() { + return Lists.newArrayList(Iterables.transform(surname, new Capitalize())); + } + + public List getNameWithAbbreviations() { + return Lists.newArrayList(Iterables.transform(firstname, new DotAbbreviations())); + } + + public boolean isAccurate() { + return (firstname != null && surname != null && !firstname.isEmpty() && !surname.isEmpty()); + } + + @Override + public QName getName() { + return new QName(QNAME_BASE_URI + "/person", "person"); + } + + @Override + public SequenceType getResultType() { + return SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ZERO_OR_ONE); + } + + @Override + public SequenceType[] getArgumentTypes() { + // TODO Auto-generated method stub + return null; + } + + @Override + public XdmValue call(XdmValue[] arguments) throws SaxonApiException { + // TODO Auto-generated method stub + return null; + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java index 430fbcf95..f803c7cbc 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java @@ -46,6 +46,7 @@ public class XSLTTransformationFunction implements MapFunction { + + @Override + public String apply(String s) { + return org.apache.commons.lang3.text.WordUtils.capitalize(s.toLowerCase()); + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/utils/DotAbbreviations.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/utils/DotAbbreviations.java new file mode 100644 index 000000000..01174bf04 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/utils/DotAbbreviations.java @@ -0,0 +1,12 @@ + +package eu.dnetlib.dhp.transformation.xslt.utils; + +import com.google.common.base.Function; + +public class DotAbbreviations implements Function { + + @Override + public String apply(String s) { + return s.length() == 1 ? s + "." : s; + } +} diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java index f3a0685ac..a46245f6d 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java @@ -92,15 +92,19 @@ public class TransformationJobTest extends AbstractVocabularyTest { } @Test - @DisplayName("Test Transform Inst.&Them.v4 record XML with xslt_cleaning_oaiOpenaire_datacite_ExchangeLandingpagePid") - public void testTransformITGv4() throws Exception { + @DisplayName("Test Transform record XML with xslt_cleaning_datarepo_datacite/oaiOpenAIRE") + public void testTransformMostlyUsedScript() throws Exception { + + String xslTransformationScript = ""; + xslTransformationScript = "/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_datarepo_datacite.xsl"; + xslTransformationScript = "/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_oaiOpenaire_datacite_ExchangeLandingpagePid.xsl"; + // We Set the input Record getting the XML from the classpath final MetadataRecord mr = new MetadataRecord(); mr.setBody(IOUtils.toString(getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/input_itgv4.xml"))); // We Load the XSLT transformation Rule from the classpath - XSLTTransformationFunction tr = loadTransformationRule( - "/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_oaiOpenaire_datacite_ExchangeLandingpagePid.xsl"); + XSLTTransformationFunction tr = loadTransformationRule(xslTransformationScript); MetadataRecord result = tr.call(mr); @@ -110,15 +114,18 @@ public class TransformationJobTest extends AbstractVocabularyTest { } @Test - @DisplayName("Test Transform record XML with xslt_cleaning_datarepo_datacite") - public void testTransformMostlyUsedScript() throws Exception { + @DisplayName("Test Transform record XML with xslt_cleaning_REST_OmicsDI") + public void testTransformRestScript() throws Exception { + + String xslTransformationScript = ""; + xslTransformationScript = "/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_REST_OmicsDI.xsl"; + // We Set the input Record getting the XML from the classpath final MetadataRecord mr = new MetadataRecord(); - mr.setBody(IOUtils.toString(getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/input_itgv4.xml"))); + mr.setBody(IOUtils.toString(getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/input_omicsdi.xml"))); // We Load the XSLT transformation Rule from the classpath - XSLTTransformationFunction tr = loadTransformationRule( - "/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_datarepo_datacite.xsl"); + XSLTTransformationFunction tr = loadTransformationRule(xslTransformationScript); MetadataRecord result = tr.call(mr); diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input_omicsdi.xml b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input_omicsdi.xml new file mode 100644 index 000000000..b068b89e3 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input_omicsdi.xml @@ -0,0 +1,60 @@ + + + + _____OmicsDI::0000337c02d1b51030675d69407655da + PRJNA78295 + 2020-10-31T15:31:30.725Z + _____OmicsDI + + + + 0.235294117647059 + 0 + null + 0.0 + 0 + Sedimentitalea nanhaiensis DSM 24252 Genome sequencing and assembly + 8.20101314054644E-5 + omics_ena_project + Sedimentitalea nanhaiensis DSM 24252 + 14 + 0 + null + Genomics + 0.0 + + 571166 + Sedimentitalea nanhaiensis DSM 24252 + + 0.0 + false + PRJNA78295 + null + 13 + + + + + + https%3A%2F%2Fwww.omicsdi.org%2Fws%2Fdataset%2Fsearch + + + + + + + false + false + 0.9 + + + + + diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_REST_OmicsDI.xsl b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_REST_OmicsDI.xsl new file mode 100644 index 000000000..4ac24183f --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_REST_OmicsDI.xsl @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + record is not compliant, transformation is interrupted. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UNKNOWN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From d442e25cbcc34f9d78252a2f71763c9da1e65e96 Mon Sep 17 00:00:00 2001 From: miconis Date: Mon, 12 Apr 2021 15:56:22 +0200 Subject: [PATCH 204/445] bug fix: ids in self mergerels are not marked deletedbyinference=true --- .../src/main/java/eu/dnetlib/dhp/oa/dedup/SparkUpdateEntity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkUpdateEntity.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkUpdateEntity.java index cda4137ba..03709c8fe 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkUpdateEntity.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/SparkUpdateEntity.java @@ -91,6 +91,7 @@ public class SparkUpdateEntity extends AbstractSparkAction { final JavaPairRDD mergedIds = rel .where("relClass == 'merges'") + .where("source != target") .select(rel.col("target")) .distinct() .toJavaRDD() From 902d05f54818259437dd9e84e7613258542ee2c1 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 12 Apr 2021 17:31:40 +0200 Subject: [PATCH 205/445] [cleaning] avoiding NPEs handling null author PIDs --- .../java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index b9d89a82b..917733a14 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -58,9 +58,9 @@ public class CleaningFunctions { } } if (Objects.nonNull(r.getAuthor())) { - r.getAuthor().forEach(a -> { + r.getAuthor().stream().filter(Objects::nonNull).forEach(a -> { if (Objects.nonNull(a.getPid())) { - a.getPid().forEach(p -> { + a.getPid().stream().filter(Objects::nonNull).forEach(p -> { fixVocabName(p.getQualifier(), ModelConstants.DNET_PID_TYPES); }); } @@ -218,6 +218,7 @@ public class CleaningFunctions { a .getPid() .stream() + .filter(Objects::nonNull) .filter(p -> Objects.nonNull(p.getQualifier())) .filter(p -> StringUtils.isNotBlank(p.getValue())) .map(p -> { From 511c0521e5ae7cf34715f23ca2b14efc78360c9d Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Mon, 12 Apr 2021 17:45:11 +0200 Subject: [PATCH 206/445] [dedup] avoiding NPEs handling OpenOrg relations --- .../java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java index e8e67567b..647a1b9c8 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java +++ b/dhp-workflows/dhp-dedup-openaire/src/main/java/eu/dnetlib/dhp/oa/dedup/AbstractSparkAction.java @@ -6,6 +6,7 @@ import java.io.Serializable; import java.io.StringReader; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -137,7 +138,8 @@ abstract class AbstractSparkAction implements Serializable { .map( c -> c .stream() - .filter(kv -> kv.getValue().equals(ModelConstants.OPENORGS_NAME)) + .filter(Objects::nonNull) + .filter(kv -> ModelConstants.OPENORGS_NAME.equals(kv.getValue())) .findFirst() .isPresent()) .orElse(false); From 3b694074ffab57fda01d9fc58b6d5bde9a847132 Mon Sep 17 00:00:00 2001 From: Andreas Czerniak Date: Fri, 9 Apr 2021 14:35:30 +0200 Subject: [PATCH 207/445] add xslt, personname cleaner --- .../transformation/xslt/PersonCleaner.java | 206 ++++++++++++ .../xslt/XSLTTransformationFunction.java | 1 + .../transformation/xslt/utils/Capitalize.java | 14 + .../xslt/utils/DotAbbreviations.java | 12 + .../transformation/TransformationJobTest.java | 25 +- .../dnetlib/dhp/transform/input_omicsdi.xml | 60 ++++ .../scripts/xslt_cleaning_REST_OmicsDI.xsl | 297 ++++++++++++++++++ 7 files changed, 606 insertions(+), 9 deletions(-) create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/PersonCleaner.java create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/utils/Capitalize.java create mode 100644 dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/utils/DotAbbreviations.java create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input_omicsdi.xml create mode 100644 dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_REST_OmicsDI.xsl diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/PersonCleaner.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/PersonCleaner.java new file mode 100644 index 000000000..069060722 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/PersonCleaner.java @@ -0,0 +1,206 @@ + +package eu.dnetlib.dhp.transformation.xslt; + +import static eu.dnetlib.dhp.transformation.xslt.XSLTTransformationFunction.QNAME_BASE_URI; + +import java.io.Serializable; +// import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.text.Normalizer; +import java.util.List; +import java.util.Set; + +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.hash.Hashing; + +import eu.dnetlib.dhp.transformation.xslt.utils.Capitalize; +import eu.dnetlib.dhp.transformation.xslt.utils.DotAbbreviations; +import net.sf.saxon.s9api.ExtensionFunction; +import net.sf.saxon.s9api.ItemType; +import net.sf.saxon.s9api.OccurrenceIndicator; +import net.sf.saxon.s9api.QName; +import net.sf.saxon.s9api.SaxonApiException; +import net.sf.saxon.s9api.SequenceType; +import net.sf.saxon.s9api.XdmValue; + +//import eu.dnetlib.pace.clustering.NGramUtils; +//import eu.dnetlib.pace.util.Capitalise; +//import eu.dnetlib.pace.util.DotAbbreviations; + +public class PersonCleaner implements ExtensionFunction, Serializable { + /** + * + */ + private static final long serialVersionUID = 1L; + private List firstname = Lists.newArrayList(); + private List surname = Lists.newArrayList(); + private List fullname = Lists.newArrayList(); + + private static Set particles = null; + + public PersonCleaner() { + + } + + public String normalize(String s) { + s = Normalizer.normalize(s, Normalizer.Form.NFD); // was NFD + s = s.replaceAll("\\(.+\\)", ""); + s = s.replaceAll("\\[.+\\]", ""); + s = s.replaceAll("\\{.+\\}", ""); + s = s.replaceAll("\\s+-\\s+", "-"); + +// s = s.replaceAll("[\\W&&[^,-]]", " "); + +// System.out.println("class Person: s: " + s); + +// s = s.replaceAll("[\\p{InCombiningDiacriticalMarks}&&[^,-]]", " "); + s = s.replaceAll("[\\p{Punct}&&[^-,]]", " "); + s = s.replace("\\d", " "); + s = s.replace("\\n", " "); + s = s.replace("\\.", " "); + s = s.replaceAll("\\s+", " "); + + if (s.contains(",")) { + // System.out.println("class Person: s: " + s); + + String[] arr = s.split(","); + if (arr.length == 1) { + + fullname = splitTerms(arr[0]); + } else if (arr.length > 1) { + surname = splitTerms(arr[0]); + firstname = splitTermsFirstName(arr[1]); +// System.out.println("class Person: surname: " + surname); +// System.out.println("class Person: firstname: " + firstname); + + fullname.addAll(surname); + fullname.addAll(firstname); + } + } else { + fullname = splitTerms(s); + + int lastInitialPosition = fullname.size(); + boolean hasSurnameInUpperCase = false; + + for (int i = 0; i < fullname.size(); i++) { + String term = fullname.get(i); + if (term.length() == 1) { + lastInitialPosition = i; + } else if (term.equals(term.toUpperCase())) { + hasSurnameInUpperCase = true; + } + } + if (lastInitialPosition < fullname.size() - 1) { // Case: Michele G. Artini + firstname = fullname.subList(0, lastInitialPosition + 1); + System.out.println("name: " + firstname); + surname = fullname.subList(lastInitialPosition + 1, fullname.size()); + } else if (hasSurnameInUpperCase) { // Case: Michele ARTINI + for (String term : fullname) { + if (term.length() > 1 && term.equals(term.toUpperCase())) { + surname.add(term); + } else { + firstname.add(term); + } + } + } else if (lastInitialPosition == fullname.size()) { + surname = fullname.subList(lastInitialPosition - 1, fullname.size()); + firstname = fullname.subList(0, lastInitialPosition - 1); + } + + } + return null; + } + + private List splitTermsFirstName(String s) { + List list = Lists.newArrayList(); + for (String part : Splitter.on(" ").omitEmptyStrings().split(s)) { + if (s.trim().matches("\\p{Lu}{2,3}")) { + String[] parts = s.trim().split("(?=\\p{Lu})"); // (Unicode UpperCase) + for (String p : parts) { + if (p.length() > 0) + list.add(p); + } + } else { + list.add(part); + } + + } + return list; + } + + private List splitTerms(String s) { + if (particles == null) { + // particles = NGramUtils.loadFromClasspath("/eu/dnetlib/pace/config/name_particles.txt"); + } + + List list = Lists.newArrayList(); + for (String part : Splitter.on(" ").omitEmptyStrings().split(s)) { + // if (!particles.contains(part.toLowerCase())) { + list.add(part); + + // } + } + return list; + } + + public List getFirstname() { + return firstname; + } + + public List getSurname() { + return surname; + } + + public List getFullname() { + return fullname; + } + + public String hash() { + return Hashing.murmur3_128().hashString(getNormalisedFullname(), StandardCharsets.UTF_8).toString(); + } + + public String getNormalisedFullname() { + return isAccurate() ? Joiner.on(" ").join(getSurname()) + ", " + Joiner.on(" ").join(getNameWithAbbreviations()) + : Joiner.on(" ").join(fullname); + // return isAccurate() ? + // Joiner.on(" ").join(getCapitalSurname()) + ", " + Joiner.on(" ").join(getNameWithAbbreviations()) : + // Joiner.on(" ").join(fullname); + } + + public List getCapitalSurname() { + return Lists.newArrayList(Iterables.transform(surname, new Capitalize())); + } + + public List getNameWithAbbreviations() { + return Lists.newArrayList(Iterables.transform(firstname, new DotAbbreviations())); + } + + public boolean isAccurate() { + return (firstname != null && surname != null && !firstname.isEmpty() && !surname.isEmpty()); + } + + @Override + public QName getName() { + return new QName(QNAME_BASE_URI + "/person", "person"); + } + + @Override + public SequenceType getResultType() { + return SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ZERO_OR_ONE); + } + + @Override + public SequenceType[] getArgumentTypes() { + // TODO Auto-generated method stub + return null; + } + + @Override + public XdmValue call(XdmValue[] arguments) throws SaxonApiException { + // TODO Auto-generated method stub + return null; + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java index 430fbcf95..f803c7cbc 100644 --- a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/XSLTTransformationFunction.java @@ -46,6 +46,7 @@ public class XSLTTransformationFunction implements MapFunction { + + @Override + public String apply(String s) { + return org.apache.commons.lang3.text.WordUtils.capitalize(s.toLowerCase()); + } +} diff --git a/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/utils/DotAbbreviations.java b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/utils/DotAbbreviations.java new file mode 100644 index 000000000..01174bf04 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/main/java/eu/dnetlib/dhp/transformation/xslt/utils/DotAbbreviations.java @@ -0,0 +1,12 @@ + +package eu.dnetlib.dhp.transformation.xslt.utils; + +import com.google.common.base.Function; + +public class DotAbbreviations implements Function { + + @Override + public String apply(String s) { + return s.length() == 1 ? s + "." : s; + } +} diff --git a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java index f3a0685ac..a46245f6d 100644 --- a/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java +++ b/dhp-workflows/dhp-aggregation/src/test/java/eu/dnetlib/dhp/transformation/TransformationJobTest.java @@ -92,15 +92,19 @@ public class TransformationJobTest extends AbstractVocabularyTest { } @Test - @DisplayName("Test Transform Inst.&Them.v4 record XML with xslt_cleaning_oaiOpenaire_datacite_ExchangeLandingpagePid") - public void testTransformITGv4() throws Exception { + @DisplayName("Test Transform record XML with xslt_cleaning_datarepo_datacite/oaiOpenAIRE") + public void testTransformMostlyUsedScript() throws Exception { + + String xslTransformationScript = ""; + xslTransformationScript = "/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_datarepo_datacite.xsl"; + xslTransformationScript = "/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_oaiOpenaire_datacite_ExchangeLandingpagePid.xsl"; + // We Set the input Record getting the XML from the classpath final MetadataRecord mr = new MetadataRecord(); mr.setBody(IOUtils.toString(getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/input_itgv4.xml"))); // We Load the XSLT transformation Rule from the classpath - XSLTTransformationFunction tr = loadTransformationRule( - "/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_oaiOpenaire_datacite_ExchangeLandingpagePid.xsl"); + XSLTTransformationFunction tr = loadTransformationRule(xslTransformationScript); MetadataRecord result = tr.call(mr); @@ -110,15 +114,18 @@ public class TransformationJobTest extends AbstractVocabularyTest { } @Test - @DisplayName("Test Transform record XML with xslt_cleaning_datarepo_datacite") - public void testTransformMostlyUsedScript() throws Exception { + @DisplayName("Test Transform record XML with xslt_cleaning_REST_OmicsDI") + public void testTransformRestScript() throws Exception { + + String xslTransformationScript = ""; + xslTransformationScript = "/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_REST_OmicsDI.xsl"; + // We Set the input Record getting the XML from the classpath final MetadataRecord mr = new MetadataRecord(); - mr.setBody(IOUtils.toString(getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/input_itgv4.xml"))); + mr.setBody(IOUtils.toString(getClass().getResourceAsStream("/eu/dnetlib/dhp/transform/input_omicsdi.xml"))); // We Load the XSLT transformation Rule from the classpath - XSLTTransformationFunction tr = loadTransformationRule( - "/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_datarepo_datacite.xsl"); + XSLTTransformationFunction tr = loadTransformationRule(xslTransformationScript); MetadataRecord result = tr.call(mr); diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input_omicsdi.xml b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input_omicsdi.xml new file mode 100644 index 000000000..b068b89e3 --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/input_omicsdi.xml @@ -0,0 +1,60 @@ + + + + _____OmicsDI::0000337c02d1b51030675d69407655da + PRJNA78295 + 2020-10-31T15:31:30.725Z + _____OmicsDI + + + + 0.235294117647059 + 0 + null + 0.0 + 0 + Sedimentitalea nanhaiensis DSM 24252 Genome sequencing and assembly + 8.20101314054644E-5 + omics_ena_project + Sedimentitalea nanhaiensis DSM 24252 + 14 + 0 + null + Genomics + 0.0 + + 571166 + Sedimentitalea nanhaiensis DSM 24252 + + 0.0 + false + PRJNA78295 + null + 13 + + + + + + https%3A%2F%2Fwww.omicsdi.org%2Fws%2Fdataset%2Fsearch + + + + + + + false + false + 0.9 + + + + + diff --git a/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_REST_OmicsDI.xsl b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_REST_OmicsDI.xsl new file mode 100644 index 000000000..4ac24183f --- /dev/null +++ b/dhp-workflows/dhp-aggregation/src/test/resources/eu/dnetlib/dhp/transform/scripts/xslt_cleaning_REST_OmicsDI.xsl @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + record is not compliant, transformation is interrupted. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UNKNOWN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From d7614c1f85418c69331687045004daa3b54713b4 Mon Sep 17 00:00:00 2001 From: Andreas Czerniak Date: Tue, 13 Apr 2021 07:04:20 +0200 Subject: [PATCH 208/445] introduce new const --- dhp-common/src/main/java/eu/dnetlib/dhp/common/Constants.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/common/Constants.java b/dhp-common/src/main/java/eu/dnetlib/dhp/common/Constants.java index eb4cb91ed..108edad47 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/common/Constants.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/common/Constants.java @@ -42,6 +42,8 @@ public class Constants { public static final String RETRY_DELAY = "retryDelay"; public static final String CONNECT_TIMEOUT = "connectTimeOut"; public static final String READ_TIMEOUT = "readTimeOut"; + public static final String FROM_DATE_OVERRIDE = "fromDateOverride"; + public static final String UNTIL_DATE_OVERRIDE = "untilDateOverride"; public static final String CONTENT_TOTALITEMS = "TotalItems"; public static final String CONTENT_INVALIDRECORDS = "InvalidRecords"; From 369ed1cd8a9a33d7e4a10d1d3cac9d44626f1be3 Mon Sep 17 00:00:00 2001 From: miconis Date: Tue, 13 Apr 2021 09:08:05 +0200 Subject: [PATCH 209/445] bug fix: lookupurl parameter added to dedup record job --- .../eu/dnetlib/dhp/oa/dedup/copyOpenorgs_parameters.json | 6 ++++++ .../eu/dnetlib/dhp/oa/dedup/SparkOpenorgsProvisionTest.java | 2 ++ 2 files changed, 8 insertions(+) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/copyOpenorgs_parameters.json b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/copyOpenorgs_parameters.json index e45efca01..ceb80a52c 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/copyOpenorgs_parameters.json +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/copyOpenorgs_parameters.json @@ -22,5 +22,11 @@ "paramLongName": "numPartitions", "paramDescription": "number of partitions for the similarity relations intermediate phases", "paramRequired": false + }, + { + "paramName": "la", + "paramLongName": "isLookUpUrl", + "paramDescription": "the url for the lookup service", + "paramRequired": true } ] \ No newline at end of file diff --git a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsProvisionTest.java b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsProvisionTest.java index 9ff287c42..41f81547e 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsProvisionTest.java +++ b/dhp-workflows/dhp-dedup-openaire/src/test/java/eu/dnetlib/dhp/oa/dedup/SparkOpenorgsProvisionTest.java @@ -161,6 +161,8 @@ public class SparkOpenorgsProvisionTest implements Serializable { testGraphBasePath, "-asi", testActionSetId, + "-la", + "lookupurl", "-w", testOutputBasePath }); From 1542196a33deb02ba20491927e38b76c8eb92337 Mon Sep 17 00:00:00 2001 From: miconis Date: Tue, 13 Apr 2021 10:15:43 +0200 Subject: [PATCH 210/445] bug fix: starting node of duplicate scan wf changed --- .../eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml index b86bc009c..342d83e8e 100644 --- a/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-dedup-openaire/src/main/resources/eu/dnetlib/dhp/oa/dedup/scan/oozie_app/workflow.xml @@ -83,7 +83,7 @@ - + Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] From d1ca025b0bdbdff02901b4dd6514dce397d16ee3 Mon Sep 17 00:00:00 2001 From: Claudio Atzori Date: Tue, 13 Apr 2021 14:32:41 +0200 Subject: [PATCH 211/445] [cleaning] remiving authors without fullname or providing 'deactivated' keyword. Removing test test titles --- .../dhp/schema/oaf/CleaningFunctions.java | 79 ++++++++++++++++--- .../oa/graph/clean/CleanGraphSparkJob.java | 1 + .../oa/graph/clean/CleaningFunctionTest.java | 4 + .../eu/dnetlib/dhp/oa/graph/clean/result.json | 22 ++++++ 4 files changed, 96 insertions(+), 10 deletions(-) diff --git a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java index 917733a14..673bee314 100644 --- a/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java +++ b/dhp-common/src/main/java/eu/dnetlib/dhp/schema/oaf/CleaningFunctions.java @@ -4,6 +4,7 @@ package eu.dnetlib.dhp.schema.oaf; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; @@ -22,6 +23,9 @@ public class CleaningFunctions { public static final String CLEANING_REGEX = "(?:\\n|\\r|\\t)"; public static final Set PID_BLACKLIST = new HashSet<>(); + public static final String INVALID_AUTHOR_REGEX = ".*deactivated.*"; + public static final String TITLE_FILTER_REGEX = "[.*test.*\\W\\d]"; + public static final int TITLE_FILTER_RESIDUAL_LENGTH = 10; static { PID_BLACKLIST.add("none"); @@ -80,6 +84,36 @@ public class CleaningFunctions { return value; } + public static boolean filter(T value) { + if (value instanceof Datasource) { + // nothing to evaluate here + } else if (value instanceof Project) { + // nothing to evaluate here + } else if (value instanceof Organization) { + // nothing to evaluate here + } else if (value instanceof Relation) { + // nothing to clean here + } else if (value instanceof Result) { + + Result r = (Result) value; + + if (Objects.nonNull(r.getTitle()) && r.getTitle().isEmpty()) { + return false; + } + + if (value instanceof Publication) { + + } else if (value instanceof eu.dnetlib.dhp.schema.oaf.Dataset) { + + } else if (value instanceof OtherResearchProduct) { + + } else if (value instanceof Software) { + + } + } + return true; + } + public static T cleanup(T value) { if (value instanceof Datasource) { // nothing to clean here @@ -124,6 +158,12 @@ public class CleaningFunctions { .stream() .filter(Objects::nonNull) .filter(sp -> StringUtils.isNotBlank(sp.getValue())) + .filter( + sp -> sp + .getValue() + .toLowerCase() + .replaceAll(TITLE_FILTER_REGEX, "") + .length() > TITLE_FILTER_RESIDUAL_LENGTH) .map(CleaningFunctions::cleanValue) .collect(Collectors.toList())); } @@ -199,16 +239,7 @@ public class CleaningFunctions { } } if (Objects.nonNull(r.getAuthor())) { - boolean nullRank = r - .getAuthor() - .stream() - .anyMatch(a -> Objects.isNull(a.getRank())); - if (nullRank) { - int i = 1; - for (Author author : r.getAuthor()) { - author.setRank(i++); - } - } + final List authors = Lists.newArrayList(); for (Author a : r.getAuthor()) { if (Objects.isNull(a.getPid())) { a.setPid(Lists.newArrayList()); @@ -235,8 +266,27 @@ public class CleaningFunctions { .stream() .collect(Collectors.toList())); } + if (StringUtils.isBlank(a.getFullname())) { + if (StringUtils.isNotBlank(a.getName()) && StringUtils.isNotBlank(a.getSurname())) { + a.setFullname(a.getSurname() + ", " + a.getName()); + } + } + if (StringUtils.isNotBlank(a.getFullname()) && isValidAuthorName(a)) { + authors.add(a); + } } + boolean nullRank = authors + .stream() + .anyMatch(a -> Objects.isNull(a.getRank())); + if (nullRank) { + int i = 1; + for (Author author : authors) { + author.setRank(i++); + } + } + r.setAuthor(authors); + } if (value instanceof Publication) { @@ -252,6 +302,15 @@ public class CleaningFunctions { return value; } + private static boolean isValidAuthorName(Author a) { + return !Stream + .of(a.getFullname(), a.getName(), a.getSurname()) + .filter(s -> s != null && !s.isEmpty()) + .collect(Collectors.joining("")) + .toLowerCase() + .matches(INVALID_AUTHOR_REGEX); + } + private static List processPidCleaning(List pids) { return pids .stream() diff --git a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java index 86c453656..088539325 100644 --- a/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java +++ b/dhp-workflows/dhp-graph-mapper/src/main/java/eu/dnetlib/dhp/oa/graph/clean/CleanGraphSparkJob.java @@ -90,6 +90,7 @@ public class CleanGraphSparkJob { .map((MapFunction) value -> fixVocabularyNames(value), Encoders.bean(clazz)) .map((MapFunction) value -> OafCleaner.apply(value, mapping), Encoders.bean(clazz)) .map((MapFunction) value -> cleanup(value), Encoders.bean(clazz)) + .filter((FilterFunction) value -> filter(value)) .write() .mode(SaveMode.Overwrite) .option("compression", "gzip") diff --git a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java index fdbc58c17..15cb054ad 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java +++ b/dhp-workflows/dhp-graph-mapper/src/test/java/eu/dnetlib/dhp/oa/graph/clean/CleaningFunctionTest.java @@ -67,6 +67,7 @@ public class CleaningFunctionTest { assertNotNull(p_out.getPublisher()); assertNull(p_out.getPublisher().getValue()); + assertEquals("und", p_out.getLanguage().getClassid()); assertEquals("Undetermined", p_out.getLanguage().getClassname()); @@ -120,6 +121,9 @@ public class CleaningFunctionTest { .isPresent()); Publication p_cleaned = CleaningFunctions.cleanup(p_out); + + assertEquals(1, p_cleaned.getTitle().size()); + assertEquals("CLOSED", p_cleaned.getBestaccessright().getClassid()); assertNull(p_out.getPublisher()); diff --git a/dhp-workflows/dhp-graph-mapper/src/test/resources/eu/dnetlib/dhp/oa/graph/clean/result.json b/dhp-workflows/dhp-graph-mapper/src/test/resources/eu/dnetlib/dhp/oa/graph/clean/result.json index 23de2ef86..8670c10f1 100644 --- a/dhp-workflows/dhp-graph-mapper/src/test/resources/eu/dnetlib/dhp/oa/graph/clean/result.json +++ b/dhp-workflows/dhp-graph-mapper/src/test/resources/eu/dnetlib/dhp/oa/graph/clean/result.json @@ -865,6 +865,28 @@ "schemename": "dnet:dataCite_title" }, "value": "Optical response of strained- and unstrained-silicon cold-electron bolometers" + }, + { + "dataInfo": { + "deletedbyinference": false, + "inferenceprovenance": "", + "inferred": false, + "invisible": false, + "provenanceaction": { + "classid": "sysimport:crosswalk:datasetarchive", + "classname": "sysimport:crosswalk:datasetarchive", + "schemeid": "dnet:provenanceActions", + "schemename": "dnet:provenanceActions" + }, + "trust": "0.9" + }, + "qualifier": { + "classid": "main title", + "classname": "main title", + "schemeid": "dnet:dataCite_title", + "schemename": "dnet:dataCite_title" + }, + "value": "test test 123 test" } ] } \ No newline at end of file From 479abd10cb64ba499775f239b6980e91bdca07e8 Mon Sep 17 00:00:00 2001 From: Sandro La Bruzzo Date: Tue, 13 Apr 2021 17:47:43 +0200 Subject: [PATCH 212/445] Add into ORCID workflow a method that extracts orcid directly to the dump generated by Enrico --- .../dnetlib/doiboost/orcid/ORCIDToOAF.scala | 85 ++++++++++++++---- .../orcid/SparkConvertORCIDToOAF.scala | 74 ++++++--------- .../doiboost/convert_map_to_oaf_params.json | 3 +- .../dhp/doiboost/oozie_app/workflow.xml | 6 ++ .../orcid/MappingORCIDToOAFTest.scala | 57 +++++++----- .../doiboost/orcid/datasets/authors/result.gz | Bin 0 -> 2436050 bytes .../doiboost/orcid/datasets/works/part-00000 | 59 ++++++++++++ 7 files changed, 198 insertions(+), 86 deletions(-) create mode 100644 dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/datasets/authors/result.gz create mode 100644 dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/datasets/works/part-00000 diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala index 8cf6efb49..05660d461 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/ORCIDToOAF.scala @@ -4,16 +4,23 @@ import com.fasterxml.jackson.databind.ObjectMapper import eu.dnetlib.dhp.schema.common.ModelConstants import eu.dnetlib.dhp.schema.oaf.utils.IdentifierFactory import eu.dnetlib.dhp.schema.oaf.{Author, DataInfo, Publication} -import eu.dnetlib.dhp.schema.orcid.OrcidDOI +import eu.dnetlib.dhp.schema.orcid.{AuthorData, OrcidDOI} import eu.dnetlib.doiboost.DoiBoostMappingUtil import eu.dnetlib.doiboost.DoiBoostMappingUtil.{createSP, generateDataInfo} import org.apache.commons.lang.StringUtils import org.slf4j.{Logger, LoggerFactory} import scala.collection.JavaConverters._ +import org.json4s +import org.json4s.DefaultFormats +import org.json4s.JsonAST._ +import org.json4s.jackson.JsonMethods._ -case class ORCIDItem(oid:String,name:String,surname:String,creditName:String,errorCode:String){} +case class ORCIDItem(doi:String, authors:List[OrcidAuthor]){} +case class OrcidAuthor(oid:String, name:Option[String], surname:Option[String], creditName:Option[String], otherNames:Option[List[String]], errorCode:Option[String]){} +case class OrcidWork(oid:String, doi:String) + @@ -46,8 +53,52 @@ object ORCIDToOAF { } - def convertTOOAF(input:OrcidDOI) :Publication = { - val doi = input.getDoi + def strValid(s:Option[String]) : Boolean = { + s.isDefined && s.get.nonEmpty + } + + def authorValid(author:OrcidAuthor): Boolean ={ + if (strValid(author.name) && strValid(author.surname)) { + return true + } + if (strValid(author.surname)) { + return true + } + if (strValid(author.creditName)) { + return true + + } + false + } + + + def extractDOIWorks(input:String): List[OrcidWork] = { + implicit lazy val formats: DefaultFormats.type = org.json4s.DefaultFormats + lazy val json: json4s.JValue = parse(input) + + val oid = (json \ "workDetail" \"oid").extract[String] + val doi:List[(String, String)] = for { + JObject(extIds) <- json \ "workDetail" \"extIds" + JField("type", JString(typeValue)) <- extIds + JField("value", JString(value)) <- extIds + if "doi".equalsIgnoreCase(typeValue) + } yield (typeValue, value) + if (doi.nonEmpty) { + return doi.map(l =>OrcidWork(oid, l._2)) + } + List() + } + + def convertORCIDAuthor(input:String): OrcidAuthor = { + implicit lazy val formats: DefaultFormats.type = org.json4s.DefaultFormats + lazy val json: json4s.JValue = parse(input) + + (json \"authorData" ).extractOrElse[OrcidAuthor](null) + } + + + def convertTOOAF(input:ORCIDItem) :Publication = { + val doi = input.doi val pub:Publication = new Publication pub.setPid(List(createSP(doi.toLowerCase, "doi", ModelConstants.DNET_PID_TYPES)).asJava) pub.setDataInfo(generateDataInfo()) @@ -58,8 +109,8 @@ object ORCIDToOAF { try{ - val l:List[Author]= input.getAuthors.asScala.map(a=> { - generateAuthor(a.getName, a.getSurname, a.getCreditName, a.getOid) + val l:List[Author]= input.authors.map(a=> { + generateAuthor(a) })(collection.breakOut) pub.setAuthor(l.asJava) @@ -80,16 +131,20 @@ object ORCIDToOAF { di } - def generateAuthor(given: String, family: String, fullName:String, orcid: String): Author = { + def generateAuthor(o : OrcidAuthor): Author = { val a = new Author - a.setName(given) - a.setSurname(family) - if (fullName!= null && fullName.nonEmpty) - a.setFullname(fullName) - else - a.setFullname(s"$given $family") - if (StringUtils.isNotBlank(orcid)) - a.setPid(List(createSP(orcid, ModelConstants.ORCID, ModelConstants.DNET_PID_TYPES, generateOricPIDDatainfo())).asJava) + if (strValid(o.name)) { + a.setName(o.name.get.capitalize) + } + if (strValid(o.surname)) { + a.setSurname(o.surname.get.capitalize) + } + if(strValid(o.name) && strValid(o.surname)) + a.setFullname(s"${o.name.get.capitalize} ${o.surname.get.capitalize}") + else if (strValid(o.creditName)) + a.setFullname(o.creditName.get) + if (StringUtils.isNotBlank(o.oid)) + a.setPid(List(createSP(o.oid, ModelConstants.ORCID, ModelConstants.DNET_PID_TYPES, generateOricPIDDatainfo())).asJava) a } diff --git a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkConvertORCIDToOAF.scala b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkConvertORCIDToOAF.scala index f1d718d0d..64be5e79a 100644 --- a/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkConvertORCIDToOAF.scala +++ b/dhp-workflows/dhp-doiboost/src/main/java/eu/dnetlib/doiboost/orcid/SparkConvertORCIDToOAF.scala @@ -5,68 +5,48 @@ import eu.dnetlib.dhp.application.ArgumentApplicationParser import eu.dnetlib.dhp.oa.merge.AuthorMerger import eu.dnetlib.dhp.schema.oaf.Publication import eu.dnetlib.dhp.schema.orcid.OrcidDOI -import eu.dnetlib.doiboost.mag.ConversionUtil import org.apache.commons.io.IOUtils import org.apache.spark.SparkConf import org.apache.spark.rdd.RDD -import org.apache.spark.sql.expressions.Aggregator +import org.apache.spark.sql.functions._ import org.apache.spark.sql.{Dataset, Encoder, Encoders, SaveMode, SparkSession} import org.slf4j.{Logger, LoggerFactory} object SparkConvertORCIDToOAF { val logger: Logger = LoggerFactory.getLogger(SparkConvertORCIDToOAF.getClass) - def getPublicationAggregator(): Aggregator[(String, Publication), Publication, Publication] = new Aggregator[(String, Publication), Publication, Publication]{ - - override def zero: Publication = new Publication() - - override def reduce(b: Publication, a: (String, Publication)): Publication = { - b.mergeFrom(a._2) - b.setAuthor(AuthorMerger.mergeAuthor(a._2.getAuthor, b.getAuthor)) - if (b.getId == null) - b.setId(a._2.getId) - b - } - - - override def merge(wx: Publication, wy: Publication): Publication = { - wx.mergeFrom(wy) - wx.setAuthor(AuthorMerger.mergeAuthor(wy.getAuthor, wx.getAuthor)) - if(wx.getId == null && wy.getId.nonEmpty) - wx.setId(wy.getId) - wx - } - override def finish(reduction: Publication): Publication = reduction - - override def bufferEncoder: Encoder[Publication] = - Encoders.kryo(classOf[Publication]) - - override def outputEncoder: Encoder[Publication] = - Encoders.kryo(classOf[Publication]) - } - - def run(spark:SparkSession,sourcePath:String, targetPath:String):Unit = { + def run(spark:SparkSession,sourcePath:String,workingPath:String, targetPath:String):Unit = { + import spark.implicits._ implicit val mapEncoderPubs: Encoder[Publication] = Encoders.kryo[Publication] - implicit val mapOrcid: Encoder[OrcidDOI] = Encoders.kryo[OrcidDOI] - implicit val tupleForJoinEncoder: Encoder[(String, Publication)] = Encoders.tuple(Encoders.STRING, mapEncoderPubs) - val mapper = new ObjectMapper() - mapper.getDeserializationConfig.withFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + val inputRDD:RDD[OrcidAuthor] = spark.sparkContext.textFile(s"$sourcePath/authors").map(s => ORCIDToOAF.convertORCIDAuthor(s)).filter(s => s!= null).filter(s => ORCIDToOAF.authorValid(s)) - val dataset:Dataset[OrcidDOI] = spark.createDataset(spark.sparkContext.textFile(sourcePath).map(s => mapper.readValue(s,classOf[OrcidDOI]))) + spark.createDataset(inputRDD).as[OrcidAuthor].write.mode(SaveMode.Overwrite).save(s"$workingPath/author") + + val res = spark.sparkContext.textFile(s"$sourcePath/works").flatMap(s => ORCIDToOAF.extractDOIWorks(s)).filter(s => s!= null) + + spark.createDataset(res).as[OrcidWork].write.mode(SaveMode.Overwrite).save(s"$workingPath/works") + + val authors :Dataset[OrcidAuthor] = spark.read.load(s"$workingPath/author").as[OrcidAuthor] + + val works :Dataset[OrcidWork] = spark.read.load(s"$workingPath/works").as[OrcidWork] + + works.joinWith(authors, authors("oid").equalTo(works("oid"))) + .map(i =>{ + val doi = i._1.doi + val author = i._2 + (doi, author) + }).groupBy(col("_1").alias("doi")) + .agg(collect_list(col("_2")).alias("authors")) + .write.mode(SaveMode.Overwrite).save(s"$workingPath/orcidworksWithAuthor") + + val dataset: Dataset[ORCIDItem] =spark.read.load(s"$workingPath/orcidworksWithAuthor").as[ORCIDItem] logger.info("Converting ORCID to OAF") - dataset.map(o => ORCIDToOAF.convertTOOAF(o)).filter(p=>p!=null) - .map(d => (d.getId, d)) - .groupByKey(_._1)(Encoders.STRING) - .agg(getPublicationAggregator().toColumn) - .map(p => p._2) - .write.mode(SaveMode.Overwrite).save(targetPath) + dataset.map(o => ORCIDToOAF.convertTOOAF(o)).write.mode(SaveMode.Overwrite).save(targetPath) } def main(args: Array[String]): Unit = { - - val conf: SparkConf = new SparkConf() val parser = new ArgumentApplicationParser(IOUtils.toString(SparkConvertORCIDToOAF.getClass.getResourceAsStream("/eu/dnetlib/dhp/doiboost/convert_map_to_oaf_params.json"))) parser.parseArgument(args) @@ -78,10 +58,10 @@ object SparkConvertORCIDToOAF { .master(parser.get("master")).getOrCreate() - val sourcePath = parser.get("sourcePath") + val workingPath = parser.get("workingPath") val targetPath = parser.get("targetPath") - run(spark, sourcePath, targetPath) + run(spark, sourcePath, workingPath, targetPath) } diff --git a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/convert_map_to_oaf_params.json b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/convert_map_to_oaf_params.json index 312bd0751..8ca7ac89e 100644 --- a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/convert_map_to_oaf_params.json +++ b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/convert_map_to_oaf_params.json @@ -1,5 +1,6 @@ [ - {"paramName":"s", "paramLongName":"sourcePath", "paramDescription": "the path of the sequencial file to read", "paramRequired": true}, + {"paramName":"s", "paramLongName":"sourcePath", "paramDescription": "the path of the Orcid Input file", "paramRequired": true}, + {"paramName":"w", "paramLongName":"workingPath", "paramDescription": "the working path ", "paramRequired": true}, {"paramName":"t", "paramLongName":"targetPath", "paramDescription": "the working dir path", "paramRequired": true}, {"paramName":"m", "paramLongName":"master", "paramDescription": "the master name", "paramRequired": true} diff --git a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/oozie_app/workflow.xml b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/oozie_app/workflow.xml index cc260d7c0..6cb8a577a 100644 --- a/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/oozie_app/workflow.xml +++ b/dhp-workflows/dhp-doiboost/src/main/resources/eu/dnetlib/dhp/doiboost/oozie_app/workflow.xml @@ -74,6 +74,11 @@ inputPathOrcid + the ORCID input path + + + + workingPathOrcid the ORCID working path @@ -295,6 +300,7 @@ --conf spark.eventLog.dir=${nameNode}${spark2EventLogDir} --sourcePath${inputPathOrcid} + --workingPath${workingPathOrcid} --targetPath${workingPath}/orcidPublication --masteryarn-cluster diff --git a/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/orcid/MappingORCIDToOAFTest.scala b/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/orcid/MappingORCIDToOAFTest.scala index a1b3d06b7..826591470 100644 --- a/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/orcid/MappingORCIDToOAFTest.scala +++ b/dhp-workflows/dhp-doiboost/src/test/java/eu/dnetlib/doiboost/orcid/MappingORCIDToOAFTest.scala @@ -2,12 +2,13 @@ package eu.dnetlib.doiboost.orcid import com.fasterxml.jackson.databind.ObjectMapper import eu.dnetlib.dhp.schema.oaf.Publication -import eu.dnetlib.doiboost.orcid.SparkConvertORCIDToOAF.getClass -import org.apache.spark.sql.{Encoder, Encoders, SparkSession} +import org.apache.spark.sql.{Dataset, Encoder, Encoders, SparkSession} import org.junit.jupiter.api.Assertions._ import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir import org.slf4j.{Logger, LoggerFactory} +import java.nio.file.Path import scala.io.Source class MappingORCIDToOAFTest { @@ -24,27 +25,37 @@ class MappingORCIDToOAFTest { }) } -// @Test -// def testOAFConvert():Unit ={ -// -// val spark: SparkSession = -// SparkSession -// .builder() -// .appName(getClass.getSimpleName) -// .master("local[*]").getOrCreate() -// -// -// SparkConvertORCIDToOAF.run( spark,"/Users/sandro/Downloads/orcid", "/Users/sandro/Downloads/orcid_oaf") -// implicit val mapEncoderPubs: Encoder[Publication] = Encoders.kryo[Publication] -// -// val df = spark.read.load("/Users/sandro/Downloads/orcid_oaf").as[Publication] -// println(df.first.getId) -// println(mapper.writeValueAsString(df.first())) -// -// -// -// -// } + @Test + def testOAFConvert(@TempDir testDir: Path):Unit ={ + val sourcePath:String = getClass.getResource("/eu/dnetlib/doiboost/orcid/datasets").getPath + val targetPath: String =s"${testDir.toString}/output/orcidPublication" + val workingPath =s"${testDir.toString}/wp/" + + val spark: SparkSession = + SparkSession + .builder() + .appName(getClass.getSimpleName) + .master("local[*]").getOrCreate() + implicit val mapEncoderPubs: Encoder[Publication] = Encoders.kryo[Publication] + import spark.implicits._ + + SparkConvertORCIDToOAF.run( spark,sourcePath, workingPath, targetPath) + + val mapper = new ObjectMapper() + + + + val oA = spark.read.load(s"$workingPath/orcidworksWithAuthor").as[ORCIDItem].count() + + + + val p: Dataset[Publication] = spark.read.load(targetPath).as[Publication] + + assertTrue(oA == p.count()) + println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(p.first())) + + + } diff --git a/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/datasets/authors/result.gz b/dhp-workflows/dhp-doiboost/src/test/resources/eu/dnetlib/doiboost/orcid/datasets/authors/result.gz new file mode 100644 index 0000000000000000000000000000000000000000..e85f9af2c425b2b0845ba03624c6a5cec2aca2a0 GIT binary patch literal 2436050 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 z

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 z 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

cP5S~jNUxBujFtD|lidoL@*GqtR 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

gP+)Cv-T96FenE=31R;c*>cYwF~>3 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 Yhcx*Ml#l5~V{3fKU~fjb4l= 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