diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/controller/AdminController.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/controller/AdminController.java index 0e835d19..943ceadf 100644 --- a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/controller/AdminController.java +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/controller/AdminController.java @@ -22,6 +22,7 @@ import eu.dnetlib.organizations.importer.ImportExecution; import eu.dnetlib.organizations.importer.ImportExecutor; import eu.dnetlib.organizations.model.SystemConfiguration; import eu.dnetlib.organizations.model.utils.VocabularyTerm; +import eu.dnetlib.organizations.model.view.PersistentOrganizationView; import eu.dnetlib.organizations.model.view.UserView; import eu.dnetlib.organizations.repository.SystemConfigurationRepository; import eu.dnetlib.organizations.repository.UserRepository; @@ -202,4 +203,22 @@ public class AdminController extends AbstractDnetController { throw new RuntimeException("User not authorized"); } } + + @GetMapping("/api/persistentOrgs") + public Iterable listPersistentOrgs() { + return dbUtils.listPersistentOrgs(); + } + + @PostMapping("/api/persistentOrgs") + public Iterable addPersistentOrgs(@RequestBody final List ids) { + ids.forEach(id -> dbUtils.addPersistentOrgs(id)); + return dbUtils.listPersistentOrgs(); + } + + @DeleteMapping("/api/persistentOrgs") + public Iterable deletePersistentOrgs(@RequestParam final String id) { + dbUtils.deletePersistentOrgs(id); + return dbUtils.listPersistentOrgs(); + } + } diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/controller/HomeController.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/controller/HomeController.java index db337ffd..7d76fb8d 100644 --- a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/controller/HomeController.java +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/controller/HomeController.java @@ -11,6 +11,7 @@ import org.springframework.security.core.Authentication; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; @@ -30,6 +31,9 @@ public class HomeController extends AbstractDnetController { @Autowired private SystemConfigurationRepository systemConfigurationRepository; + @Value("${openaire.explore.organization.baseurl}") + private String openaireBaseUrl; + @Value("${openorgs.support.pages}") private String supportPagesJson; @@ -40,6 +44,11 @@ public class HomeController extends AbstractDnetController { return env.acceptsProfiles(Profiles.of("dev")) ? "redirect:main" : "home"; } + @GetMapping("/redirect/oa/{orgId}") + public String openaireUrl(@PathVariable final String orgId) { + return "redirect:" + String.format(openaireBaseUrl, orgId); + } + @GetMapping("/main") public String main() { return "main"; diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/controller/OrganizationController.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/controller/OrganizationController.java index 9ecd3cca..eb706c5f 100644 --- a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/controller/OrganizationController.java +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/controller/OrganizationController.java @@ -17,7 +17,6 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.security.core.Authentication; @@ -83,8 +82,6 @@ public class OrganizationController extends AbstractDnetController { private JournalEntryRepository journalEntryRepository; @Autowired private DatabaseUtils databaseUtils; - @Value("${openaire.explore.organization.baseurl}") - private String oaBaseUrl; @PostMapping("/save") public List save(@RequestBody final OrganizationView org, final Authentication authentication) { @@ -107,9 +104,7 @@ public class OrganizationController extends AbstractDnetController { @GetMapping("/info") public OrganizationInfoView infoById(@RequestParam final String id, final Authentication authentication) { - final OrganizationInfoView info = organizationInfoViewRepository.findById(id).get(); - info.fillGraphNodeInfo(info.getId(), oaBaseUrl); - return info; + return organizationInfoViewRepository.findById(id).get(); } @GetMapping("/suggestionsInfo") @@ -160,9 +155,7 @@ public class OrganizationController extends AbstractDnetController { } private List listDuplicates(final String id) { - final List list = openaireDuplicateViewRepository.findByLocalId(id); - list.forEach(d -> d.fillGraphNodeInfo(d.getOaOriginalId(), oaBaseUrl)); - return list; + return openaireDuplicateViewRepository.findByLocalId(id); } @GetMapping("/conflicts/byCountry/{country}") diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/PersistentOrganization.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/PersistentOrganization.java new file mode 100644 index 00000000..b01ea8a1 --- /dev/null +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/PersistentOrganization.java @@ -0,0 +1,34 @@ +package eu.dnetlib.organizations.model; + +import java.io.Serializable; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "persistent_orgs") +public class PersistentOrganization implements Serializable { + + private static final long serialVersionUID = 7684478315366015099L; + + @Id + @Column(name = "id") + private String id; + + public PersistentOrganization() {} + + public PersistentOrganization(final String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + +} diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/utils/OpenaireGraphNode.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/utils/OpenaireGraphNode.java index 6867942b..1b34ab01 100644 --- a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/utils/OpenaireGraphNode.java +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/utils/OpenaireGraphNode.java @@ -1,23 +1,31 @@ package eu.dnetlib.organizations.model.utils; -import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.lang3.StringUtils; +import javax.persistence.Column; +import javax.persistence.MappedSuperclass; -public interface OpenaireGraphNode { +@MappedSuperclass +public abstract class OpenaireGraphNode { - String getOaGraphId(); + @Column(name = "openaire_id") + private String openaireId; - void setOaGraphId(String oaGraphId); + @Column(name = "openaire_persistent") + private Boolean persistent = false; - String getOaGraphUrl(); + public String getOpenaireId() { + return openaireId; + } - void setOaGraphUrl(String oaGraphUrl); + public void setOpenaireId(final String openaireId) { + this.openaireId = openaireId; + } - default void fillGraphNodeInfo(final String origId, final String baseUrl) { - final String oaGraphId = StringUtils.substringBefore(origId, "::") + "::" - + DigestUtils.md5Hex(StringUtils.substringAfter(origId, "::")); - setOaGraphId(oaGraphId); - setOaGraphUrl(baseUrl + oaGraphId); + public Boolean getPersistent() { + return persistent; + } + + public void setPersistent(final Boolean persistent) { + this.persistent = persistent; } } diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/view/OpenaireDuplicateView.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/view/OpenaireDuplicateView.java index 2a02ed35..9cfba1a3 100644 --- a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/view/OpenaireDuplicateView.java +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/view/OpenaireDuplicateView.java @@ -8,7 +8,6 @@ import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.IdClass; import javax.persistence.Table; -import javax.persistence.Transient; import org.hibernate.annotations.Type; @@ -18,7 +17,7 @@ import eu.dnetlib.organizations.model.utils.OpenaireGraphNode; @Entity @Table(name = "oa_duplicates_view") @IdClass(OpenaireDuplicatePK.class) -public class OpenaireDuplicateView implements Serializable, OpenaireGraphNode { +public class OpenaireDuplicateView extends OpenaireGraphNode implements Serializable { /** * @@ -88,12 +87,6 @@ public class OpenaireDuplicateView implements Serializable, OpenaireGraphNode { @Column(name = "ec_nutscode") private Boolean ecNutscode; - @Transient - private String oaGraphId; - - @Transient - private String oaGraphUrl; - public String getLocalId() { return localId; } @@ -254,24 +247,4 @@ public class OpenaireDuplicateView implements Serializable, OpenaireGraphNode { this.ecNutscode = ecNutscode; } - @Override - public String getOaGraphId() { - return oaGraphId; - } - - @Override - public void setOaGraphId(final String oaGraphId) { - this.oaGraphId = oaGraphId; - } - - @Override - public String getOaGraphUrl() { - return oaGraphUrl; - } - - @Override - public void setOaGraphUrl(final String oaGraphUrl) { - this.oaGraphUrl = oaGraphUrl; - } - } diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/view/OrganizationInfoView.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/view/OrganizationInfoView.java index 7109d8a1..3ca41df6 100644 --- a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/view/OrganizationInfoView.java +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/view/OrganizationInfoView.java @@ -7,13 +7,12 @@ import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; -import javax.persistence.Transient; import eu.dnetlib.organizations.model.utils.OpenaireGraphNode; @Entity @Table(name = "organizations_info_view") -public class OrganizationInfoView implements Serializable, OpenaireGraphNode { +public class OrganizationInfoView extends OpenaireGraphNode implements Serializable { /** * @@ -48,12 +47,6 @@ public class OrganizationInfoView implements Serializable, OpenaireGraphNode { @Column(name = "note") private boolean note; - @Transient - private String oaGraphId; - - @Transient - private String oaGraphUrl; - public String getId() { return id; } @@ -126,24 +119,4 @@ public class OrganizationInfoView implements Serializable, OpenaireGraphNode { this.note = note; } - @Override - public String getOaGraphId() { - return oaGraphId; - } - - @Override - public void setOaGraphId(final String oaGraphId) { - this.oaGraphId = oaGraphId; - } - - @Override - public String getOaGraphUrl() { - return oaGraphUrl; - } - - @Override - public void setOaGraphUrl(final String oaGraphUrl) { - this.oaGraphUrl = oaGraphUrl; - } - } diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/view/OrganizationView.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/view/OrganizationView.java index 06d92519..cdf41c94 100644 --- a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/view/OrganizationView.java +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/view/OrganizationView.java @@ -15,13 +15,15 @@ import org.hibernate.annotations.TypeDefs; import com.vladmihalcea.hibernate.type.json.JsonBinaryType; import com.vladmihalcea.hibernate.type.json.JsonStringType; +import eu.dnetlib.organizations.model.utils.OpenaireGraphNode; + @Entity @Table(name = "organizations_view") @TypeDefs({ @TypeDef(name = "json", typeClass = JsonStringType.class), @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) }) -public class OrganizationView implements Serializable { +public class OrganizationView extends OpenaireGraphNode implements Serializable { /** * diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/view/PersistentOrganizationView.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/view/PersistentOrganizationView.java new file mode 100644 index 00000000..872fa06a --- /dev/null +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/model/view/PersistentOrganizationView.java @@ -0,0 +1,63 @@ +package eu.dnetlib.organizations.model.view; + +import java.io.Serializable; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import eu.dnetlib.organizations.model.utils.OpenaireGraphNode; + +@Entity +@Table(name = "persistent_orgs_view") +public class PersistentOrganizationView extends OpenaireGraphNode implements Serializable { + + private static final long serialVersionUID = -8906936709574708538L; + + @Id + @Column(name = "id") + private String id; + + @Column(name = "name") + private String name; + + @Column(name = "city") + private String city; + + @Column(name = "country") + private String country; + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public String getCity() { + return city; + } + + public void setCity(final String city) { + this.city = city; + } + + public String getCountry() { + return country; + } + + public void setCountry(final String country) { + this.country = country; + } + +} diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/repository/OrganizationRepository.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/repository/OrganizationRepository.java index 6071b0d4..30301239 100644 --- a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/repository/OrganizationRepository.java +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/repository/OrganizationRepository.java @@ -22,9 +22,10 @@ public interface OrganizationRepository extends JpaRepository { + +} diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/repository/readonly/PersistentOrganizationViewRepository.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/repository/readonly/PersistentOrganizationViewRepository.java new file mode 100644 index 00000000..c53dd904 --- /dev/null +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/repository/readonly/PersistentOrganizationViewRepository.java @@ -0,0 +1,10 @@ +package eu.dnetlib.organizations.repository.readonly; + +import org.springframework.stereotype.Repository; + +import eu.dnetlib.organizations.model.view.PersistentOrganizationView; + +@Repository +public interface PersistentOrganizationViewRepository extends ReadOnlyRepository { + +} diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/utils/DatabaseUtils.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/utils/DatabaseUtils.java index 4e9c3c07..6b62494c 100644 --- a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/utils/DatabaseUtils.java +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/utils/DatabaseUtils.java @@ -12,10 +12,12 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; import javax.transaction.Transactional; +import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; @@ -37,6 +39,7 @@ import eu.dnetlib.organizations.model.OpenaireDuplicate; import eu.dnetlib.organizations.model.Organization; import eu.dnetlib.organizations.model.OtherIdentifier; import eu.dnetlib.organizations.model.OtherName; +import eu.dnetlib.organizations.model.PersistentOrganization; import eu.dnetlib.organizations.model.Relationship; import eu.dnetlib.organizations.model.Url; import eu.dnetlib.organizations.model.User; @@ -46,6 +49,7 @@ import eu.dnetlib.organizations.model.utils.OrganizationConflict; import eu.dnetlib.organizations.model.utils.TempBrowseEntry; import eu.dnetlib.organizations.model.utils.VocabularyTerm; import eu.dnetlib.organizations.model.view.OrganizationView; +import eu.dnetlib.organizations.model.view.PersistentOrganizationView; import eu.dnetlib.organizations.model.view.UserView; import eu.dnetlib.organizations.repository.AcronymRepository; import eu.dnetlib.organizations.repository.JournalEntryRepository; @@ -54,11 +58,13 @@ import eu.dnetlib.organizations.repository.OpenaireDuplicateRepository; import eu.dnetlib.organizations.repository.OrganizationRepository; import eu.dnetlib.organizations.repository.OtherIdentifierRepository; import eu.dnetlib.organizations.repository.OtherNameRepository; +import eu.dnetlib.organizations.repository.PersistentOrganizationRepository; import eu.dnetlib.organizations.repository.RelationshipRepository; import eu.dnetlib.organizations.repository.UrlRepository; import eu.dnetlib.organizations.repository.UserCountryRepository; import eu.dnetlib.organizations.repository.UserRepository; import eu.dnetlib.organizations.repository.readonly.OrganizationViewRepository; +import eu.dnetlib.organizations.repository.readonly.PersistentOrganizationViewRepository; @Component public class DatabaseUtils { @@ -87,6 +93,10 @@ public class DatabaseUtils { private OrganizationViewRepository organizationViewRepository; @Autowired private JournalEntryRepository journalEntryRepository; + @Autowired + private PersistentOrganizationRepository persistentOrganizationRepository; + @Autowired + private PersistentOrganizationViewRepository persistentOrganizationViewRepository; @Autowired private JdbcTemplate jdbcTemplate; @@ -129,7 +139,7 @@ public class DatabaseUtils { final String pendingId = OpenOrgsConstants.OPENORGS_PENDING_PREFIX + UUID.randomUUID(); orgView.setId(pendingId); // to override the generation strategy of the ID - organizationRepository.preparePendingOrg(pendingId); + organizationRepository.prepareOrgWithId(pendingId); } else { orgView.setId(null); // The ID is generated by the DB } @@ -468,45 +478,97 @@ public class DatabaseUtils { @Transactional public String fixConflictSimilars(final List similarIds, final String user) { - final OffsetDateTime now = OffsetDateTime.now(); - final List views = similarIds.stream().map(organizationViewRepository::findById).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()); - // I create a new org - final OrganizationView newOrg = new OrganizationView(); - newOrg.setId(null); - newOrg.setStatus(null); - newOrg.setName(findFirstString(views, OrganizationView::getName)); - newOrg.setType(findFirstString(views, OrganizationView::getType)); - newOrg.setLat(findFirstNumber(views, OrganizationView::getLat)); - newOrg.setLng(findFirstNumber(views, OrganizationView::getLng)); - newOrg.setCity(findFirstString(views, OrganizationView::getCity)); - newOrg.setCountry(findFirstString(views, OrganizationView::getCountry)); - newOrg.setOtherIdentifiers(findAll(views, OrganizationView::getOtherIdentifiers)); - newOrg.setOtherNames(findAll(views, OrganizationView::getOtherNames)); - newOrg.setAcronyms(findAll(views, OrganizationView::getAcronyms)); - newOrg.setUrls(findAll(views, OrganizationView::getUrls)); - newOrg.setRelations(findAll(views, OrganizationView::getRelations)); + final List persistents = views.stream().filter(v -> v.getPersistent()).collect(Collectors.toList()); - newOrg.getOtherNames() - .addAll(views.stream() + final OrganizationView masterOrg = new OrganizationView(); + + if (persistents.size() > 1) { + throw new RuntimeException("Too many persintent organizations"); + } else if (persistents.size() == 1) { + backupOrg(persistents.get(0), user); + masterOrg.setId(persistents.get(0).getId()); + masterOrg.setStatus(OrganizationStatus.approved.toString()); + } else { + masterOrg.setId(null); + masterOrg.setStatus(null); + } + return fixConflicts(masterOrg, views, user); + } + + private String backupOrg(final OrganizationView org, final String user) { + final String origId = org.getId(); + final String backupId = origId + "::" + OffsetDateTime.now().toEpochSecond(); + + organizationRepository.prepareOrgWithId(backupId); + try { + + final OrganizationView backupOrg = (OrganizationView) BeanUtils.cloneBean(org); + backupOrg.setId(backupId); + + insertOrUpdateOrganization(backupOrg, user, false); + + organizationRepository.updateStatus(backupId, OrganizationStatus.hidden.toString()); + + journalEntryRepository + .save(new JournalEntry(origId, JournalOperations.BACKUP_ORG, "Saved a backup copy: " + backupId, user)); + journalEntryRepository + .save(new JournalEntry(backupId, JournalOperations.BACKUP_ORG, "Saved a backup copy of " + origId, user)); + + return backupId; + } catch (final Exception e) { + log.error("Error performing the backup of " + origId, e); + throw new RuntimeException("Error performing the backup of " + origId, e); + } + } + + private String fixConflicts(final OrganizationView masterOrg, final List orgs, final String user) { + + final OffsetDateTime now = OffsetDateTime.now(); + + final String finalMessage = (masterOrg.getId() == null ? "New org created merging: " : "Merging in persistent org: ") + + orgs.stream() + .map(OrganizationView::getId) + .collect(Collectors.joining(", ")); + + masterOrg.setName(findFirstString(orgs, OrganizationView::getName)); + masterOrg.setType(findFirstString(orgs, OrganizationView::getType)); + masterOrg.setLat(findFirstNumber(orgs, OrganizationView::getLat)); + masterOrg.setLng(findFirstNumber(orgs, OrganizationView::getLng)); + masterOrg.setCity(findFirstString(orgs, OrganizationView::getCity)); + masterOrg.setCountry(findFirstString(orgs, OrganizationView::getCountry)); + masterOrg.setOtherIdentifiers(findAll(orgs, OrganizationView::getOtherIdentifiers)); + masterOrg.setOtherNames(findAll(orgs, OrganizationView::getOtherNames)); + masterOrg.setAcronyms(findAll(orgs, OrganizationView::getAcronyms)); + masterOrg.setUrls(findAll(orgs, OrganizationView::getUrls)); + masterOrg.setRelations(findAll(orgs, OrganizationView::getRelations, r -> !r.getType().equals(RelationType.Merged_In.toString()) + && !r.getType().equals(RelationType.Merged_In.toString()))); + + masterOrg.getOtherNames() + .addAll(orgs.stream() .map(OrganizationView::getName) .filter(StringUtils::isNotBlank) - .filter(s -> StringUtils.equalsIgnoreCase(s, newOrg.getName())) + .filter(s -> StringUtils.equalsIgnoreCase(s, masterOrg.getName())) .map(s -> new eu.dnetlib.organizations.model.view.OtherName(s, "UNKNOWN")) .collect(Collectors.toList())); - final String masterId = insertOrUpdateOrganization(newOrg, user, false); + final String masterId = insertOrUpdateOrganization(masterOrg, user, false); // I hide the merged organizations - similarIds.forEach(id -> { - hideConflictOrgs(masterId, id); - journalEntryRepository.save(new JournalEntry(masterId, JournalOperations.FIX_CONFLICT, "The org has been hidded and merged in " + masterId, user)); - }); + orgs.stream() + .map(OrganizationView::getId) + .filter(id -> !id.equals(masterId)) + .forEach(id -> { + hideConflictOrgs(masterId, id); + journalEntryRepository + .save(new JournalEntry(id, JournalOperations.FIX_CONFLICT, "The org has been hidded and merged in " + masterId, user)); + }); // I reassign the duplicates to the new org - final List newDuplicates = similarIds.stream() + final List newDuplicates = orgs.stream() + .map(OrganizationView::getId) .map(openaireDuplicateRepository::findByLocalId) .flatMap(l -> l.stream()) .map(d -> new OpenaireDuplicate(masterId, d.getOaOriginalId(), d.getRelType(), d.getOaCollectedFrom())) @@ -518,18 +580,20 @@ public class DatabaseUtils { openaireDuplicateRepository.updateModificationDate(d.getLocalId(), d.getOaOriginalId(), user, now); }); - for (final String similarId : similarIds) { + orgs.forEach(org -> { + final String similarId = org.getId(); openaireConflictRepository.updateMultipleStatusAndResetGroup(similarId, SimilarityType.is_different.toString(), user, now); - } + }); - for (int i = 0; i < similarIds.size(); i++) { - for (int j = i + 1; j < similarIds.size(); j++) { - openaireConflictRepository.updateStatusAndResetGroup(similarIds.get(i), similarIds.get(j), SimilarityType.is_similar.toString(), user, now); + for (int i = 0; i < orgs.size(); i++) { + for (int j = i + 1; j < orgs.size(); j++) { + openaireConflictRepository + .updateStatusAndResetGroup(orgs.get(i).getId(), orgs.get(j).getId(), SimilarityType.is_similar.toString(), user, now); } } journalEntryRepository - .save(new JournalEntry(masterId, JournalOperations.FIX_CONFLICT, "New org created merging: " + StringUtils.join(similarIds, ", "), user)); + .save(new JournalEntry(masterId, JournalOperations.FIX_CONFLICT, finalMessage, user)); return masterId; } @@ -572,6 +636,10 @@ public class DatabaseUtils { return views.stream().map(mapper).flatMap(s -> s.stream()).collect(Collectors.toCollection(LinkedHashSet::new)); } + private Set findAll(final List views, final Function> mapper, final Predicate filter) { + return views.stream().map(mapper).flatMap(s -> s.stream()).filter(filter).collect(Collectors.toCollection(LinkedHashSet::new)); + } + private List hideConflictOrgs(final String masterId, final String otherId) { organizationRepository.updateStatus(otherId, OrganizationStatus.hidden.toString()); openaireConflictRepository.findById(new OpenaireConflictPK(masterId, otherId)).ifPresent(openaireConflictRepository::delete); @@ -585,4 +653,24 @@ public class DatabaseUtils { return jdbcTemplate.queryForList(sql, String.class); } + public Iterable listPersistentOrgs() { + return persistentOrganizationViewRepository.findAll(); + } + + public void addPersistentOrgs(final String id) { + final boolean valid = organizationRepository.findById(id) + .map(Organization::getStatus) + .filter(s -> s.equals(OrganizationStatus.approved.toString())) + .isPresent(); + if (valid) { + persistentOrganizationRepository.save(new PersistentOrganization(id)); + } else { + throw new RuntimeException("The ID does not refer to an approved Organization"); + } + } + + public void deletePersistentOrgs(final String id) { + persistentOrganizationRepository.deleteById(id); + } + } diff --git a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/utils/JournalOperations.java b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/utils/JournalOperations.java index d2db9715..7bcb1ec7 100644 --- a/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/utils/JournalOperations.java +++ b/apps/dnet-orgs-database-application/src/main/java/eu/dnetlib/organizations/utils/JournalOperations.java @@ -9,5 +9,6 @@ public enum JournalOperations { DUPLICATES, FIX_CONFLICT, NO_CONFLICT, + BACKUP_ORG, UNKNOWN } diff --git a/apps/dnet-orgs-database-application/src/main/resources/application.properties b/apps/dnet-orgs-database-application/src/main/resources/application.properties index 6425e130..83038eca 100644 --- a/apps/dnet-orgs-database-application/src/main/resources/application.properties +++ b/apps/dnet-orgs-database-application/src/main/resources/application.properties @@ -51,4 +51,4 @@ openorgs.support.pages = { "Ask a question": "https://www.openaire.eu/support/he openaire.override.logout.url = -openaire.explore.organization.baseurl = https://explore.openaire.eu/search/organization?organizationId= +openaire.explore.organization.baseurl = https://explore.openaire.eu/search/organization?organizationId=%s diff --git a/apps/dnet-orgs-database-application/src/main/resources/sql/schema.sql b/apps/dnet-orgs-database-application/src/main/resources/sql/schema.sql index cb91a260..50c9e0f9 100644 --- a/apps/dnet-orgs-database-application/src/main/resources/sql/schema.sql +++ b/apps/dnet-orgs-database-application/src/main/resources/sql/schema.sql @@ -5,6 +5,7 @@ DROP VIEW IF EXISTS users_view; DROP VIEW IF EXISTS conflict_groups_view; DROP VIEW IF EXISTS suggestions_info_by_country_view; DROP VIEW IF EXISTS duplicate_groups_view; +DROP VIEW IF EXISTS persistent_orgs_view; DROP TABLE IF EXISTS sysconf; DROP TABLE IF EXISTS other_ids; @@ -14,6 +15,8 @@ DROP TABLE IF EXISTS relationships; DROP TABLE IF EXISTS urls; DROP TABLE IF EXISTS oa_duplicates; DROP TABLE IF EXISTS oa_conflicts; +DROP TABLE IF EXISTS persistent_orgs; + DROP TABLE IF EXISTS organizations; DROP TABLE IF EXISTS org_types; @@ -374,6 +377,10 @@ CREATE TABLE organizations ( CREATE INDEX organizations_type_idx ON organizations(type); CREATE INDEX organizations_country_idx ON organizations(country); +CREATE TABLE persistent_orgs ( + id text PRIMARY KEY REFERENCES organizations(id) ON UPDATE CASCADE ON DELETE CASCADE +); + CREATE TABLE other_ids ( id text REFERENCES organizations(id) ON UPDATE CASCADE ON DELETE CASCADE, otherid text, @@ -464,7 +471,9 @@ CREATE VIEW oa_duplicates_view AS o.ec_internationalorganization, o.ec_enterprise, o.ec_smevalidated, - o.ec_nutscode + o.ec_nutscode, + substr(d.oa_original_id, 1, 14)||md5(substr(d.oa_original_id, 15)) as openaire_id, + false as openaire_persistent FROM oa_duplicates d LEFT OUTER JOIN organizations o ON (o.id = d.oa_original_id) @@ -524,6 +533,8 @@ CREATE VIEW organizations_view AS SELECT org.ec_enterprise, org.ec_smevalidated, org.ec_nutscode, + substr(org.id, 1, 14)||md5(substr(org.id, 15)) as openaire_id, + count(po.id) > 0 as openaire_persistent, COALESCE(jsonb_agg(DISTINCT jsonb_build_object('id', oid.otherid, 'type', oid.type)) FILTER (WHERE oid.otherid IS NOT NULL), '[]') AS other_ids, COALESCE(jsonb_agg(DISTINCT jsonb_build_object('name', n.name, 'lang', n.lang)) FILTER (WHERE n.name IS NOT NULL), '[]') AS other_names, COALESCE(jsonb_agg(DISTINCT a.acronym) FILTER (WHERE a.acronym IS NOT NULL), '[]') AS acronyms, @@ -537,6 +548,7 @@ FROM LEFT OUTER JOIN urls u ON (org.id = u.id) LEFT OUTER JOIN relationships r ON (org.id = r.id1) LEFT OUTER JOIN organizations relorg ON (relorg.id = r.id2) + LEFT OUTER JOIN persistent_orgs po ON (org.id = po.id) GROUP BY org.id, org.name, @@ -564,13 +576,16 @@ CREATE VIEW organizations_info_view AS SELECT org.creation_date, org.modified_by, org.modification_date, + substr(org.id, 1, 14)||md5(substr(org.id, 15)) as openaire_id, + count(po.id) > 0 as openaire_persistent, count(DISTINCT d.oa_original_id) as n_duplicates, count(DISTINCT c.id2) as n_conflicts, count(DISTINCT n.note) > 0 as note FROM organizations org - LEFT OUTER JOIN oa_duplicates d ON (org.id = d.local_id AND d.reltype = 'suggested') - LEFT OUTER JOIN oa_conflicts c ON (org.id = c.id1 AND c.reltype = 'suggested') - LEFT OUTER JOIN notes n ON (org.id = n.id) + LEFT OUTER JOIN oa_duplicates d ON (org.id = d.local_id AND d.reltype = 'suggested') + LEFT OUTER JOIN oa_conflicts c ON (org.id = c.id1 AND c.reltype = 'suggested') + LEFT OUTER JOIN notes n ON (org.id = n.id) + LEFT OUTER JOIN persistent_orgs po ON (org.id = po.id) GROUP BY org.id; CREATE VIEW organizations_simple_view AS SELECT @@ -674,6 +689,18 @@ WHERE GROUP BY o.id, o.name, o.city, o.country ORDER BY o.name; +CREATE VIEW persistent_orgs_view AS SELECT + po.id, + substr(po.id, 1, 14)||md5(substr(po.id,15)) as openaire_id, + true as openaire_persistent, + o.name, + o.city, + o.country +FROM + persistent_orgs po + JOIN organizations o ON (po.id = o.id) +ORDER BY o.name; + CREATE TABLE org_index_search(id text PRIMARY KEY, txt tsvector); CREATE INDEX org_index_search_txt_gin_idx ON org_index_search USING gin(txt); diff --git a/apps/dnet-orgs-database-application/src/main/resources/static/resources/html/pages/admin/persistentOrgs.html b/apps/dnet-orgs-database-application/src/main/resources/static/resources/html/pages/admin/persistentOrgs.html new file mode 100644 index 00000000..406d155a --- /dev/null +++ b/apps/dnet-orgs-database-application/src/main/resources/static/resources/html/pages/admin/persistentOrgs.html @@ -0,0 +1,47 @@ +

Persistent Organizations

+ +

+ It is necessary to persist the identifiers of the organizations associated to an Institutional Dashboard +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDOA Graph Node IDNamePlace
{{o.id}}{{o.openaireId}}{{o.name}} {{o.city || '-'}}, {{o.country}} + +
No persistent organizazions
+ + + +
\ No newline at end of file diff --git a/apps/dnet-orgs-database-application/src/main/resources/static/resources/html/pages/edit/edit.html b/apps/dnet-orgs-database-application/src/main/resources/static/resources/html/pages/edit/edit.html index 5d7c1599..9cac6d11 100644 --- a/apps/dnet-orgs-database-application/src/main/resources/static/resources/html/pages/edit/edit.html +++ b/apps/dnet-orgs-database-application/src/main/resources/static/resources/html/pages/edit/edit.html @@ -6,7 +6,9 @@ ID: {{info.id}}
Created at {{info.creationDate | date:'MMMM d, y HH:mm:ss'}} by {{info.createdBy}}
Modified at {{info.modificationDate | date:'MMMM d, y HH:mm:ss'}} by {{info.modifiedBy}}
- OA Graph Node ID: {{info.oaGraphId}} [try on OA Explore]
+ OA Graph Node ID: {{info.openaireId}} [try on OA Explore] + persistent +

diff --git a/apps/dnet-orgs-database-application/src/main/resources/static/resources/html/parts/org_details.html b/apps/dnet-orgs-database-application/src/main/resources/static/resources/html/parts/org_details.html index 7452777d..c5751c6e 100644 --- a/apps/dnet-orgs-database-application/src/main/resources/static/resources/html/parts/org_details.html +++ b/apps/dnet-orgs-database-application/src/main/resources/static/resources/html/parts/org_details.html @@ -4,7 +4,7 @@ 'text-white bg-success' : show == 'success', 'text-white bg-info' : show == 'info', 'bg-secondary' : show == 'secondary', - }">{{orgTitle}}
+ }">{{orgTitle}} persistent ID diff --git a/apps/dnet-orgs-database-application/src/main/resources/static/resources/html/parts/org_duplicates.html b/apps/dnet-orgs-database-application/src/main/resources/static/resources/html/parts/org_duplicates.html index 48f79291..23c22e88 100644 --- a/apps/dnet-orgs-database-application/src/main/resources/static/resources/html/parts/org_duplicates.html +++ b/apps/dnet-orgs-database-application/src/main/resources/static/resources/html/parts/org_duplicates.html @@ -36,7 +36,7 @@ diff --git a/apps/dnet-orgs-database-application/src/main/resources/static/resources/js/organizations.js b/apps/dnet-orgs-database-application/src/main/resources/static/resources/js/organizations.js index e5ec7f74..b7fb83a7 100644 --- a/apps/dnet-orgs-database-application/src/main/resources/static/resources/js/organizations.js +++ b/apps/dnet-orgs-database-application/src/main/resources/static/resources/js/organizations.js @@ -119,7 +119,12 @@ orgsModule.directive('resolveConflictsModal', function($http, $route, $window) { call_http_post($http, 'api/organizations/conflicts/fix/similar', ids, function(res) { $('#' + scope.modalId).modal('hide'); if (scope.openNewOrg == '1') { - $('#' + scope.modalId).on('hidden.bs.modal', function (e) { $window.location.assign('#!/edit/0/' + res.data[0]); }); + $('#' + scope.modalId).on('hidden.bs.modal', function (e) { + var oldLoc = $window.location; + $window.location.assign('#!/edit/0/' + res.data[0]); + var newLoc = $window.location; + if (oldLoc == newLoc) $route.reload(); + }); } else { $('#' + scope.modalId).on('hidden.bs.modal', function (e) { $route.reload(); }); } @@ -323,7 +328,13 @@ orgsModule.directive('orgConflicts', function($http, $window, $location, $route, angular.forEach(scope.conflicts, function(o, pos) { ids.push(o.id); }); if (merge) { - call_http_post($http, "api/organizations/conflicts/fix/similar", ids, function(res) { $window.location.assign('#!/edit/0/' + res.data[0]); }); + call_http_post($http, "api/organizations/conflicts/fix/similar", ids, function(res) { + if (res.data[0] == scope.org.id) { + $route.reload(); + } else { + $window.location.assign('#!/edit/0/' + res.data[0]); + } + }); } else { call_http_post($http, "api/organizations/conflicts/fix/different", ids, function(res) { $route.reload(); }); } @@ -379,6 +390,7 @@ orgsModule.config(function($routeProvider) { .when('/sysconf', { templateUrl: 'resources/html/pages/admin/sysConf.html', controller: 'sysConfCtrl' }) .when('/utils', { templateUrl: 'resources/html/pages/admin/utils.html', controller: 'utilsCtrl' }) .when('/lastImport', { templateUrl: 'resources/html/pages/admin/lastImport.html', controller: 'lastImportCtrl' }) + .when('/persistentOrgs', { templateUrl: 'resources/html/pages/admin/persistentOrgs.html', controller: 'persistentOrgsCtrl' }) .otherwise({ redirectTo: '/search' }); }); @@ -806,6 +818,26 @@ orgsModule.controller('lastImportCtrl', function ($scope, $http) { $scope.refresh(); }); +orgsModule.controller('persistentOrgsCtrl', function ($scope, $http) { + $scope.orgs = {}; + + $scope.refresh = function() { + call_http_get($http, 'api/persistentOrgs', function(res) { $scope.orgs = res.data; }); + } + + $scope.addPersistentOrg = function(id) { + call_http_post($http, 'api/persistentOrgs', [ id ], function(res) { $scope.orgs = res.data; }); + } + + $scope.deletePersistentOrg = function(id) { + if (confirm("Are you sure ?")) { + call_http_delete($http, 'api/persistentOrgs?id=' + id, function(res) { $scope.orgs = res.data; }); + } + } + + $scope.refresh(); +}); + orgsModule.controller('utilsCtrl', function ($scope, $http) { $scope.fulltextIndexMessage = ''; diff --git a/apps/dnet-orgs-database-application/src/main/resources/templates/main.html b/apps/dnet-orgs-database-application/src/main/resources/templates/main.html index 461dab24..5c3e863a 100644 --- a/apps/dnet-orgs-database-application/src/main/resources/templates/main.html +++ b/apps/dnet-orgs-database-application/src/main/resources/templates/main.html @@ -117,6 +117,8 @@ fieldset > legend { font-size : 1.2rem !important; } System utilitiesLast import of suggestions + Configure persistent organizations + Manage users
Name {{sr.oaCountry}} Original Id: {{sr.oaOriginalId}}
- OA Graph Node ID: {{sr.oaGraphId}} [try] + OA Graph Node ID: {{sr.openaireId}} [try]
Provenance: {{sr.oaCollectedFrom}}

Added by: {{sr.createdBy}}