diff --git a/dspace-api/src/main/java/org/dspace/layout/CrisLayoutBox.java b/dspace-api/src/main/java/org/dspace/layout/CrisLayoutBox.java index 47b426b135c1..9fb9a725c5b7 100644 --- a/dspace-api/src/main/java/org/dspace/layout/CrisLayoutBox.java +++ b/dspace-api/src/main/java/org/dspace/layout/CrisLayoutBox.java @@ -11,6 +11,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import javax.persistence.Cacheable; import javax.persistence.CascadeType; import javax.persistence.Column; @@ -79,13 +80,8 @@ public class CrisLayoutBox implements ReloadableEntity { ) private Set metadataSecurityFields = new HashSet<>(); - @ManyToMany(fetch = FetchType.LAZY) - @JoinTable( - name = "cris_layout_box2securitygroup", - joinColumns = {@JoinColumn(name = "box_id")}, - inverseJoinColumns = {@JoinColumn(name = "group_id")} - ) - private Set groupSecurityFields = new HashSet<>(); + @OneToMany(fetch = FetchType.LAZY, mappedBy = "box", cascade = CascadeType.ALL, orphanRemoval = true) + private Set box2SecurityGroups = new HashSet<>(); @OneToMany(fetch = FetchType.LAZY, mappedBy = "box", cascade = CascadeType.ALL) @OrderBy(value = "row, cell, priority") @@ -288,20 +284,19 @@ public void setContainer(Boolean container) { this.container = container; } - public void setGroupSecurityFields(Set groupSecurityFields) { - this.groupSecurityFields = groupSecurityFields; - } - - public void addGroupSecurityFields(Set groupSecurityFields) { - this.groupSecurityFields.addAll(groupSecurityFields); + public Set getGroupSecurityFields() { + return box2SecurityGroups.stream() + .map(crisLayoutBox2SecurityGroup -> + crisLayoutBox2SecurityGroup.getGroup()) + .collect(Collectors.toSet()); } - public void addGroupSecurityFields(Group group) { - this.groupSecurityFields.add(group); + public Set getBox2SecurityGroups() { + return box2SecurityGroups; } - public Set getGroupSecurityFields() { - return groupSecurityFields; + public void setBox2SecurityGroups(Set box2SecurityGroups) { + this.box2SecurityGroups = box2SecurityGroups; } @Override diff --git a/dspace-api/src/main/java/org/dspace/layout/CrisLayoutBox2SecurityGroup.java b/dspace-api/src/main/java/org/dspace/layout/CrisLayoutBox2SecurityGroup.java new file mode 100644 index 000000000000..d0ee1cd58415 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/layout/CrisLayoutBox2SecurityGroup.java @@ -0,0 +1,124 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.layout; + +import java.io.Serializable; +import javax.persistence.CascadeType; +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MapsId; +import javax.persistence.Table; + +import org.dspace.eperson.Group; + +@Entity +@Table(name = "cris_layout_box2securitygroup") +public class CrisLayoutBox2SecurityGroup implements Serializable { + + @Embeddable + public static class CrisLayoutBox2SecurityGroupId implements Serializable { + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "box_id") + private CrisLayoutBox boxId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "group_id") + private Group groupId; + + public CrisLayoutBox2SecurityGroupId() { + + } + + public CrisLayoutBox2SecurityGroupId(CrisLayoutBox boxId, Group groupId) { + this.boxId = boxId; + this.groupId = groupId; + } + + public CrisLayoutBox getBoxId() { + return boxId; + } + + public void setBoxId(CrisLayoutBox boxId) { + this.boxId = boxId; + } + + public Group getGroupId() { + return groupId; + } + + public void setGroupId(Group groupId) { + this.groupId = groupId; + } + } + + @EmbeddedId + private CrisLayoutBox2SecurityGroupId id; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("boxId") + @JoinColumn(name = "box_id", insertable = false, updatable = false) + private CrisLayoutBox box; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("groupId") + @JoinColumn(name = "group_id", insertable = false, updatable = false) + private Group group; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @JoinColumn(name = "alternative_box_id", nullable = true) + private CrisLayoutBox alternativeBox; + + public CrisLayoutBox2SecurityGroup() { + + } + + public CrisLayoutBox2SecurityGroup(CrisLayoutBox2SecurityGroupId id, + CrisLayoutBox box, Group group, + CrisLayoutBox alternativeBox) { + this.id = id; + this.box = box; + this.group = group; + this.alternativeBox = alternativeBox; + } + + public CrisLayoutBox2SecurityGroupId getId() { + return id; + } + + public void setId(CrisLayoutBox2SecurityGroupId id) { + this.id = id; + } + + public CrisLayoutBox getBox() { + return box; + } + + public void setBox(CrisLayoutBox box) { + this.box = box; + } + + public Group getGroup() { + return group; + } + + public void setGroup(Group group) { + this.group = group; + } + + public CrisLayoutBox getAlternativeBox() { + return alternativeBox; + } + + public void setAlternativeBox(CrisLayoutBox alternativeBox) { + this.alternativeBox = alternativeBox; + } +} diff --git a/dspace-api/src/main/java/org/dspace/layout/CrisLayoutTab.java b/dspace-api/src/main/java/org/dspace/layout/CrisLayoutTab.java index 48bd0dc56112..9c0f4ef1e2b9 100644 --- a/dspace-api/src/main/java/org/dspace/layout/CrisLayoutTab.java +++ b/dspace-api/src/main/java/org/dspace/layout/CrisLayoutTab.java @@ -87,14 +87,8 @@ public class CrisLayoutTab implements ReloadableEntity { @JoinColumn(name = "tab_id") }, inverseJoinColumns = { @JoinColumn(name = "metadata_field_id") }) private Set metadataSecurityFields = new HashSet<>(); - @ManyToMany(fetch = FetchType.LAZY) - @JoinTable( - name = "cris_layout_tab2securitygroup", - joinColumns = {@JoinColumn(name = "tab_id")}, - inverseJoinColumns = {@JoinColumn(name = "group_id")} - ) - private Set groupSecurityFields = new HashSet<>(); - + @OneToMany(fetch = FetchType.LAZY, mappedBy = "tab", cascade = CascadeType.ALL) + private Set tab2SecurityGroups = new HashSet<>(); @Column(name = "is_leading") private Boolean leading; @@ -230,20 +224,19 @@ public List getBoxes() { .collect(Collectors.toList()); } - public void setGroupSecurityFields(Set groupSecurityFields) { - this.groupSecurityFields = groupSecurityFields; - } - - public void addGroupSecurityFields(Set groupSecurityFields) { - this.groupSecurityFields.addAll(groupSecurityFields); + public Set getGroupSecurityFields() { + return tab2SecurityGroups.stream() + .map(crisLayoutTab2SecurityGroup -> + crisLayoutTab2SecurityGroup.getGroup()) + .collect(Collectors.toSet()); } - public void addGroupSecurityFields(Group group) { - this.groupSecurityFields.add(group); + public Set getTab2SecurityGroups() { + return tab2SecurityGroups; } - public Set getGroupSecurityFields() { - return groupSecurityFields; + public void setTab2SecurityGroups(Set tab2SecurityGroups) { + this.tab2SecurityGroups = tab2SecurityGroups; } @Override diff --git a/dspace-api/src/main/java/org/dspace/layout/CrisLayoutTab2SecurityGroup.java b/dspace-api/src/main/java/org/dspace/layout/CrisLayoutTab2SecurityGroup.java new file mode 100644 index 000000000000..f41b3ec53e88 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/layout/CrisLayoutTab2SecurityGroup.java @@ -0,0 +1,124 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.layout; + +import java.io.Serializable; +import javax.persistence.CascadeType; +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MapsId; +import javax.persistence.Table; + +import org.dspace.eperson.Group; + +@Entity +@Table(name = "cris_layout_tab2securitygroup") +public class CrisLayoutTab2SecurityGroup implements Serializable { + + @Embeddable + public static class CrisLayoutTab2SecurityGroupId implements Serializable { + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "tab_id") + private CrisLayoutTab tabId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "group_id") + private Group groupId; + + public CrisLayoutTab2SecurityGroupId() { + + } + + public CrisLayoutTab2SecurityGroupId(CrisLayoutTab tabId, Group groupId) { + this.tabId = tabId; + this.groupId = groupId; + } + + public CrisLayoutTab getTabId() { + return tabId; + } + + public void setTabId(CrisLayoutTab tabId) { + this.tabId = tabId; + } + + public Group getGroupId() { + return groupId; + } + + public void setGroupId(Group groupId) { + this.groupId = groupId; + } + } + + @EmbeddedId + private CrisLayoutTab2SecurityGroupId id; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("tabId") + @JoinColumn(name = "tab_id", insertable = false, updatable = false) + private CrisLayoutTab tab; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("groupId") + @JoinColumn(name = "group_id", insertable = false, updatable = false) + private Group group; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @JoinColumn(name = "alternative_tab_id") + private CrisLayoutTab alternativeTab; + + public CrisLayoutTab2SecurityGroup() { + + } + + public CrisLayoutTab2SecurityGroup(CrisLayoutTab2SecurityGroupId id, + CrisLayoutTab tab, Group group, + CrisLayoutTab alternativeTab) { + this.id = id; + this.tab = tab; + this.group = group; + this.alternativeTab = alternativeTab; + } + + public CrisLayoutTab2SecurityGroupId getId() { + return id; + } + + public void setId(CrisLayoutTab2SecurityGroupId id) { + this.id = id; + } + + public CrisLayoutTab getTab() { + return tab; + } + + public void setTab(CrisLayoutTab tab) { + this.tab = tab; + } + + public Group getGroup() { + return group; + } + + public void setGroup(Group group) { + this.group = group; + } + + public CrisLayoutTab getAlternativeTab() { + return alternativeTab; + } + + public void setAlternativeTab(CrisLayoutTab alternativeTab) { + this.alternativeTab = alternativeTab; + } +} diff --git a/dspace-api/src/main/java/org/dspace/layout/script/service/CrisLayoutToolValidator.java b/dspace-api/src/main/java/org/dspace/layout/script/service/CrisLayoutToolValidator.java index c1a9cff5dbb4..74302960cff5 100644 --- a/dspace-api/src/main/java/org/dspace/layout/script/service/CrisLayoutToolValidator.java +++ b/dspace-api/src/main/java/org/dspace/layout/script/service/CrisLayoutToolValidator.java @@ -108,6 +108,8 @@ public interface CrisLayoutToolValidator { String GROUP_COLUMN = "GROUP"; + String ALTERNATIVE_TO_COLUMN = "ALTERNATIVE_TO"; + String METADATA_TYPE = "METADATA"; String BITSTREAM_TYPE = "BITSTREAM"; diff --git a/dspace-api/src/main/java/org/dspace/layout/script/service/impl/CrisLayoutToolConverterImpl.java b/dspace-api/src/main/java/org/dspace/layout/script/service/impl/CrisLayoutToolConverterImpl.java index 1aec1c349372..52ba3ddedc16 100644 --- a/dspace-api/src/main/java/org/dspace/layout/script/service/impl/CrisLayoutToolConverterImpl.java +++ b/dspace-api/src/main/java/org/dspace/layout/script/service/impl/CrisLayoutToolConverterImpl.java @@ -31,13 +31,14 @@ import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.WorkbookFactory; import org.dspace.content.MetadataField; -import org.dspace.eperson.Group; import org.dspace.layout.CrisLayoutBox; +import org.dspace.layout.CrisLayoutBox2SecurityGroup; import org.dspace.layout.CrisLayoutCell; import org.dspace.layout.CrisLayoutField; import org.dspace.layout.CrisLayoutFieldBitstream; import org.dspace.layout.CrisLayoutMetric2Box; import org.dspace.layout.CrisLayoutTab; +import org.dspace.layout.CrisLayoutTab2SecurityGroup; import org.dspace.layout.CrisMetadataGroup; import org.dspace.layout.LayoutSecurity; import org.dspace.layout.script.service.CrisLayoutToolConverter; @@ -247,9 +248,9 @@ private void buildTabPolicy(Workbook workbook, CrisLayoutTab tab) { buildTabPolicyMetadataSecurityFieldRow(sheet, tab, metadataField) ); - tab.getGroupSecurityFields() - .forEach(group -> - buildTabPolicyGroupSecurityFieldRow(sheet, tab, group) + tab.getTab2SecurityGroups() + .forEach(tab2SecurityGroup -> + buildTabPolicyGroupSecurityFieldRow(sheet, tab, tab2SecurityGroup) ); } @@ -259,14 +260,18 @@ private void buildTabPolicyMetadataSecurityFieldRow(Sheet sheet, CrisLayoutTab t createCell(row, 1, tab.getShortName()); createCell(row, 2, metadataField.toString('.')); createCell(row, 3, ""); + createCell(row, 4, ""); } - private void buildTabPolicyGroupSecurityFieldRow(Sheet sheet, CrisLayoutTab tab, Group group) { + private void buildTabPolicyGroupSecurityFieldRow(Sheet sheet, CrisLayoutTab tab, + CrisLayoutTab2SecurityGroup tab2SecurityGroup) { + CrisLayoutTab alternativeTab = tab2SecurityGroup.getAlternativeTab(); Row row = sheet.createRow(sheet.getLastRowNum() + 1); createCell(row, 0, tab.getEntity().getLabel()); createCell(row, 1, tab.getShortName()); createCell(row, 2, ""); - createCell(row, 3, group.getName()); + createCell(row, 3, tab2SecurityGroup.getGroup().getName()); + createCell(row, 4, alternativeTab == null ? "" : alternativeTab.getShortName()); } private void buildBoxPolicy(Workbook workbook, List boxes) { @@ -277,9 +282,9 @@ private void buildBoxPolicy(Workbook workbook, List boxes) { buildBoxPolicyMetadataSecurityFieldRow(sheet, box, metadataField) ); - box.getGroupSecurityFields() - .forEach(group -> - buildBoxPolicyGroupSecurityFieldRow(sheet, box, group) + box.getBox2SecurityGroups() + .forEach(box2SecurityGroup -> + buildBoxPolicyGroupSecurityFieldRow(sheet, box, box2SecurityGroup) ); }); } @@ -290,14 +295,19 @@ private void buildBoxPolicyMetadataSecurityFieldRow(Sheet sheet, CrisLayoutBox b createCell(row, 1, box.getShortname()); createCell(row, 2, metadataField.toString('.')); createCell(row, 3, ""); + createCell(row, 4, ""); } - private void buildBoxPolicyGroupSecurityFieldRow(Sheet sheet, CrisLayoutBox box, Group group) { + private void buildBoxPolicyGroupSecurityFieldRow(Sheet sheet, CrisLayoutBox box, + CrisLayoutBox2SecurityGroup box2SecurityGroup) { + + CrisLayoutBox alternativeBox = box2SecurityGroup.getAlternativeBox(); Row row = sheet.createRow(sheet.getLastRowNum() + 1); createCell(row, 0, box.getCell().getRow().getTab().getEntity().getLabel()); createCell(row, 1, box.getShortname()); createCell(row, 2, ""); - createCell(row, 3, group.getName()); + createCell(row, 3, box2SecurityGroup.getGroup().getName()); + createCell(row, 4, alternativeBox == null ? "" : alternativeBox.getShortname()); } private String convertToString(boolean value) { diff --git a/dspace-api/src/main/java/org/dspace/layout/script/service/impl/CrisLayoutToolParserImpl.java b/dspace-api/src/main/java/org/dspace/layout/script/service/impl/CrisLayoutToolParserImpl.java index 9c368c2785b2..a4f5fec248ef 100644 --- a/dspace-api/src/main/java/org/dspace/layout/script/service/impl/CrisLayoutToolParserImpl.java +++ b/dspace-api/src/main/java/org/dspace/layout/script/service/impl/CrisLayoutToolParserImpl.java @@ -7,6 +7,7 @@ */ package org.dspace.layout.script.service.impl; +import static org.dspace.layout.script.service.CrisLayoutToolValidator.ALTERNATIVE_TO_COLUMN; import static org.dspace.layout.script.service.CrisLayoutToolValidator.BITSTREAM_TYPE; import static org.dspace.layout.script.service.CrisLayoutToolValidator.BOX2METADATA_SHEET; import static org.dspace.layout.script.service.CrisLayoutToolValidator.BOX2METRICS_SHEET; @@ -53,6 +54,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -76,6 +78,7 @@ import org.dspace.eperson.Group; import org.dspace.eperson.service.GroupService; import org.dspace.layout.CrisLayoutBox; +import org.dspace.layout.CrisLayoutBox2SecurityGroup; import org.dspace.layout.CrisLayoutBoxTypes; import org.dspace.layout.CrisLayoutCell; import org.dspace.layout.CrisLayoutField; @@ -84,6 +87,7 @@ import org.dspace.layout.CrisLayoutMetric2Box; import org.dspace.layout.CrisLayoutRow; import org.dspace.layout.CrisLayoutTab; +import org.dspace.layout.CrisLayoutTab2SecurityGroup; import org.dspace.layout.CrisMetadataGroup; import org.dspace.layout.LayoutSecurity; import org.dspace.layout.script.service.CrisLayoutToolParser; @@ -110,9 +114,21 @@ public class CrisLayoutToolParserImpl implements CrisLayoutToolParser { @Override public List parse(Context context, Workbook workbook) { Sheet tabSheet = getSheetByName(workbook, TAB_SHEET); - return WorkbookUtils.getNotEmptyRowsSkippingHeader(tabSheet).stream() - .map(row -> buildTab(context, row)) - .collect(Collectors.toList()); + List tabs = + WorkbookUtils.getNotEmptyRowsSkippingHeader(tabSheet).stream() + .map(row -> buildTab(context, row)) + .collect(Collectors.toList()); + + tabs.forEach(tab -> { + tab.setTab2SecurityGroups(buildTab2SecurityGroups(context, + workbook, TAB_POLICY_SHEET, tab.getEntity().getLabel(), tab.getShortName(), tab, tabs)); + + tab.getBoxes().forEach(box -> + box.setBox2SecurityGroups(buildBox2SecurityGroups(context, + workbook, BOX_POLICY_SHEET, box.getEntitytype().getLabel(), box.getShortname(), box, tabs))); + }); + + return tabs; } private CrisLayoutTab buildTab(Context context, Row tabRow) { @@ -136,8 +152,6 @@ private CrisLayoutTab buildTab(Context context, Row tabRow) { buildTabRows(context, workbook, entityType, name).forEach(tab::addRow); tab.setMetadataSecurityFields(buildMetadataSecurityField(context, workbook, TAB_POLICY_SHEET, entityType, name)); - tab.setGroupSecurityFields(buildGroupSecurityField(context, workbook, - TAB_POLICY_SHEET, entityType, name)); return tab; } @@ -218,8 +232,6 @@ private CrisLayoutBox buildBox(Context context, Sheet boxSheet, String entityTyp box.setStyle(getCellValue(boxRow, STYLE_COLUMN)); box.setMetadataSecurityFields(buildMetadataSecurityField(context, workbook, BOX_POLICY_SHEET, entityType, boxName)); - box.setGroupSecurityFields(buildGroupSecurityField(context, workbook, - BOX_POLICY_SHEET, entityType, boxName)); if (boxType.equals(CrisLayoutBoxTypes.METADATA.name())) { buildCrisLayoutFields(context, workbook, entityType, boxName).forEach(box::addLayoutField); @@ -376,6 +388,107 @@ private Set buildGroupSecurityField(Context context, Workbook workbook, .collect(Collectors.toSet()); } + private Set buildBox2SecurityGroups(Context context, Workbook workbook, + String sheetName, String entity, String name, + CrisLayoutBox crisLayoutBox, + List tabs) { + Sheet sheet = getSheetByName(workbook, sheetName); + Set box2SecurityGroups = new HashSet<>(); + + getRowsByEntityAndColumnValue(sheet, entity, SHORTNAME_COLUMN, name) + .forEach(row -> { + String groupName = getCellValue(row, GROUP_COLUMN); + String alternativeBox = getCellValue(row, ALTERNATIVE_TO_COLUMN); + + if (StringUtils.isNotBlank(groupName)) { + Group group = getGroupField(context, groupName); + if (group != null) { + box2SecurityGroups.add( + buildBox2SecurityGroup(group, crisLayoutBox, entity, alternativeBox, tabs) + ); + } + } + }); + + return box2SecurityGroups; + } + + private CrisLayoutBox2SecurityGroup buildBox2SecurityGroup(Group group, CrisLayoutBox box, + String entity, + String alternativeBox, List tabs) { + + CrisLayoutBox2SecurityGroup.CrisLayoutBox2SecurityGroupId box2SecurityGroupId = + new CrisLayoutBox2SecurityGroup.CrisLayoutBox2SecurityGroupId(box, group); + + return new CrisLayoutBox2SecurityGroup(box2SecurityGroupId, box, group, + findAlternativeBox(alternativeBox, entity, tabs)); + } + + private CrisLayoutBox findAlternativeBox(String alternativeBox, String entityType, List tabs) { + + if (alternativeBox == null) { + return null; + } + + return tabs.stream() + .flatMap(tab -> tab.getBoxes().stream()) + .filter(crisLayoutBox -> crisLayoutBox.getShortname().equals(alternativeBox) && + crisLayoutBox.getEntitytype().getLabel().equals(entityType)) + .findFirst() + .orElseThrow(() -> new RuntimeException("Alternative box not found for shortname: " + + alternativeBox + ", entityType: " + entityType)); + } + + private Set buildTab2SecurityGroups(Context context, Workbook workbook, + String sheetName, String entity, String name, + CrisLayoutTab crisLayoutTab, + List tabs) { + Sheet sheet = getSheetByName(workbook, sheetName); + Set tab2SecurityGroups = new HashSet<>(); + + getRowsByEntityAndColumnValue(sheet, entity, SHORTNAME_COLUMN, name) + .forEach(row -> { + String groupName = getCellValue(row, GROUP_COLUMN); + String alternativeTab = getCellValue(row, ALTERNATIVE_TO_COLUMN); + + if (StringUtils.isNotBlank(groupName)) { + Group group = getGroupField(context, groupName); + if (group != null) { + tab2SecurityGroups.add( + buildTab2SecurityGroup(group, crisLayoutTab, entity, alternativeTab, tabs) + ); + } + } + }); + + return tab2SecurityGroups; + } + + private CrisLayoutTab2SecurityGroup buildTab2SecurityGroup(Group group, CrisLayoutTab tab, + String entity, + String alternativeTab, List tabs) { + + CrisLayoutTab2SecurityGroup.CrisLayoutTab2SecurityGroupId tab2SecurityGroupId = + new CrisLayoutTab2SecurityGroup.CrisLayoutTab2SecurityGroupId(tab, group); + + return new CrisLayoutTab2SecurityGroup(tab2SecurityGroupId, tab, group, + findAlternativeTab(alternativeTab, entity, tabs)); + } + + private CrisLayoutTab findAlternativeTab(String alternativeTab, String entityType, List tabs) { + + if (alternativeTab == null) { + return null; + } + + return tabs.stream() + .filter(crisLayoutTab -> crisLayoutTab.getShortName().equals(alternativeTab) && + crisLayoutTab.getEntity().getLabel().equals(entityType)) + .findFirst() + .orElseThrow(() -> new RuntimeException("Alternative tab not found for shortname: " + + alternativeTab + ", entityType: " + entityType)); + } + private Stream getRowsByEntityAndColumnValue(Sheet sheet, String entity, String columnName, String value) { return WorkbookUtils.getNotEmptyRowsSkippingHeader(sheet).stream() .filter(row -> value.equals(getCellValue(row, columnName))) diff --git a/dspace-api/src/main/java/org/dspace/layout/service/CrisLayoutTabAccessService.java b/dspace-api/src/main/java/org/dspace/layout/service/CrisLayoutTabAccessService.java index 12d7d08084e9..2679d34865df 100644 --- a/dspace-api/src/main/java/org/dspace/layout/service/CrisLayoutTabAccessService.java +++ b/dspace-api/src/main/java/org/dspace/layout/service/CrisLayoutTabAccessService.java @@ -32,6 +32,5 @@ public interface CrisLayoutTabAccessService { * @return true if access has to be granded, false otherwise * @throws SQLException in case of error during database access */ - boolean hasAccess(Context context, EPerson user, CrisLayoutTab tab, Item item) - throws SQLException; + boolean hasAccess(Context context, EPerson user, CrisLayoutTab tab, Item item); } diff --git a/dspace-api/src/main/java/org/dspace/layout/service/CrisLayoutTabService.java b/dspace-api/src/main/java/org/dspace/layout/service/CrisLayoutTabService.java index 7224b8f26302..919dc7eb4310 100644 --- a/dspace-api/src/main/java/org/dspace/layout/service/CrisLayoutTabService.java +++ b/dspace-api/src/main/java/org/dspace/layout/service/CrisLayoutTabService.java @@ -152,4 +152,6 @@ public List getMetadataField(Context context, Integer tabId, Inte * @throws SQLException An exception that provides information on a database errors. */ public List findByItem(Context context, String itemUuid) throws SQLException; + + public boolean hasAccess(Context context, CrisLayoutTab tab, Item item); } diff --git a/dspace-api/src/main/java/org/dspace/layout/service/impl/CrisLayoutTabAccessServiceImpl.java b/dspace-api/src/main/java/org/dspace/layout/service/impl/CrisLayoutTabAccessServiceImpl.java index 71e20fd883fa..331c5df679be 100644 --- a/dspace-api/src/main/java/org/dspace/layout/service/impl/CrisLayoutTabAccessServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/layout/service/impl/CrisLayoutTabAccessServiceImpl.java @@ -31,12 +31,14 @@ public CrisLayoutTabAccessServiceImpl(LayoutSecurityService layoutSecurityServic } @Override - public boolean hasAccess(Context context, EPerson user, CrisLayoutTab tab, Item item) throws SQLException { - return layoutSecurityService.hasAccess(LayoutSecurity.valueOf(tab.getSecurity()), - context, - user, - tab.getMetadataSecurityFields(), - tab.getGroupSecurityFields(), - item); + public boolean hasAccess(Context context, EPerson user, CrisLayoutTab tab, Item item) { + try { + return layoutSecurityService.hasAccess( + LayoutSecurity.valueOf(tab.getSecurity()), context, user, tab.getMetadataSecurityFields(), + tab.getGroupSecurityFields(), item + ); + } catch (SQLException e) { + throw new RuntimeException(e); + } } } diff --git a/dspace-api/src/main/java/org/dspace/layout/service/impl/CrisLayoutTabServiceImpl.java b/dspace-api/src/main/java/org/dspace/layout/service/impl/CrisLayoutTabServiceImpl.java index 2ce7ee7ac4f4..980305e67d6e 100644 --- a/dspace-api/src/main/java/org/dspace/layout/service/impl/CrisLayoutTabServiceImpl.java +++ b/dspace-api/src/main/java/org/dspace/layout/service/impl/CrisLayoutTabServiceImpl.java @@ -29,6 +29,7 @@ import org.dspace.core.Context; import org.dspace.layout.CrisLayoutTab; import org.dspace.layout.dao.CrisLayoutTabDAO; +import org.dspace.layout.service.CrisLayoutTabAccessService; import org.dspace.layout.service.CrisLayoutTabService; import org.dspace.services.ConfigurationService; import org.springframework.beans.factory.annotation.Autowired; @@ -54,6 +55,9 @@ public class CrisLayoutTabServiceImpl implements CrisLayoutTabService { private SubmissionConfigReader submissionConfigReader; + @Autowired + CrisLayoutTabAccessService crisLayoutTabAccessService; + @PostConstruct private void setup() throws SubmissionConfigReaderException { submissionConfigReader = new SubmissionConfigReader(); @@ -217,6 +221,11 @@ public List findByItem(Context context, String itemUuid) throws S return layoutTabs; } + @Override + public boolean hasAccess(Context context, CrisLayoutTab tab, Item item) { + return crisLayoutTabAccessService.hasAccess(context, context.getCurrentUser(), tab, item); + } + private String getSubmissionDefinitionName(Item item) { if (submissionConfigReader == null || item.getOwningCollection() == null) { return ""; diff --git a/dspace-api/src/main/resources/org/dspace/layout/script/service/impl/cris-layout-configuration-template.xls b/dspace-api/src/main/resources/org/dspace/layout/script/service/impl/cris-layout-configuration-template.xls index a921219abfad..2eddc88c6777 100644 Binary files a/dspace-api/src/main/resources/org/dspace/layout/script/service/impl/cris-layout-configuration-template.xls and b/dspace-api/src/main/resources/org/dspace/layout/script/service/impl/cris-layout-configuration-template.xls differ diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.12.12__add_alternative_tab_id_to_cris_layout_tab2securitygroup.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.12.12__add_alternative_tab_id_to_cris_layout_tab2securitygroup.sql new file mode 100644 index 000000000000..6ae50fb29bf6 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.12.12__add_alternative_tab_id_to_cris_layout_tab2securitygroup.sql @@ -0,0 +1,14 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +----------------------------------------------------------------------------------- +-- Alter TABLE cris_layout_tab2securitygroup ADD alternative_tab_id +----------------------------------------------------------------------------------- + +ALTER TABLE cris_layout_tab2securitygroup ADD COLUMN alternative_tab_id INTEGER; +ALTER TABLE cris_layout_tab2securitygroup ADD CONSTRAINT cris_layout_tab2securitygroup_tab_id2 FOREIGN KEY (alternative_tab_id) REFERENCES cris_layout_tab (id) ON DELETE SET NULL; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.12.13__add_alternative_box_id_to_cris_layout_box2securitygroup.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.12.13__add_alternative_box_id_to_cris_layout_box2securitygroup.sql new file mode 100644 index 000000000000..38360bb13cd8 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/h2/V7.6_2023.12.13__add_alternative_box_id_to_cris_layout_box2securitygroup.sql @@ -0,0 +1,14 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +----------------------------------------------------------------------------------- +-- Alter TABLE cris_layout_box2securitygroup ADD alternative_box_id +----------------------------------------------------------------------------------- + +ALTER TABLE cris_layout_box2securitygroup ADD COLUMN alternative_box_id INTEGER; +ALTER TABLE cris_layout_box2securitygroup ADD CONSTRAINT cris_layout_box2securitygroup_box_id2 FOREIGN KEY (alternative_box_id) REFERENCES cris_layout_box (id) ON DELETE SET NULL; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.12.12__add_alternative_tab_id_to_cris_layout_tab2securitygroup.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.12.12__add_alternative_tab_id_to_cris_layout_tab2securitygroup.sql new file mode 100644 index 000000000000..6ae50fb29bf6 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.12.12__add_alternative_tab_id_to_cris_layout_tab2securitygroup.sql @@ -0,0 +1,14 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +----------------------------------------------------------------------------------- +-- Alter TABLE cris_layout_tab2securitygroup ADD alternative_tab_id +----------------------------------------------------------------------------------- + +ALTER TABLE cris_layout_tab2securitygroup ADD COLUMN alternative_tab_id INTEGER; +ALTER TABLE cris_layout_tab2securitygroup ADD CONSTRAINT cris_layout_tab2securitygroup_tab_id2 FOREIGN KEY (alternative_tab_id) REFERENCES cris_layout_tab (id) ON DELETE SET NULL; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.12.13__add_alternative_box_id_to_cris_layout_box2securitygroup.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.12.13__add_alternative_box_id_to_cris_layout_box2securitygroup.sql new file mode 100644 index 000000000000..38360bb13cd8 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/oracle/V7.6_2023.12.13__add_alternative_box_id_to_cris_layout_box2securitygroup.sql @@ -0,0 +1,14 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +----------------------------------------------------------------------------------- +-- Alter TABLE cris_layout_box2securitygroup ADD alternative_box_id +----------------------------------------------------------------------------------- + +ALTER TABLE cris_layout_box2securitygroup ADD COLUMN alternative_box_id INTEGER; +ALTER TABLE cris_layout_box2securitygroup ADD CONSTRAINT cris_layout_box2securitygroup_box_id2 FOREIGN KEY (alternative_box_id) REFERENCES cris_layout_box (id) ON DELETE SET NULL; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.12.12__add_alternative_tab_id_to_cris_layout_tab2securitygroup.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.12.12__add_alternative_tab_id_to_cris_layout_tab2securitygroup.sql new file mode 100644 index 000000000000..6ae50fb29bf6 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.12.12__add_alternative_tab_id_to_cris_layout_tab2securitygroup.sql @@ -0,0 +1,14 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +----------------------------------------------------------------------------------- +-- Alter TABLE cris_layout_tab2securitygroup ADD alternative_tab_id +----------------------------------------------------------------------------------- + +ALTER TABLE cris_layout_tab2securitygroup ADD COLUMN alternative_tab_id INTEGER; +ALTER TABLE cris_layout_tab2securitygroup ADD CONSTRAINT cris_layout_tab2securitygroup_tab_id2 FOREIGN KEY (alternative_tab_id) REFERENCES cris_layout_tab (id) ON DELETE SET NULL; \ No newline at end of file diff --git a/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.12.13__add_alternative_box_id_to_cris_layout_box2securitygroup.sql b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.12.13__add_alternative_box_id_to_cris_layout_box2securitygroup.sql new file mode 100644 index 000000000000..38360bb13cd8 --- /dev/null +++ b/dspace-api/src/main/resources/org/dspace/storage/rdbms/sqlmigration/postgres/V7.6_2023.12.13__add_alternative_box_id_to_cris_layout_box2securitygroup.sql @@ -0,0 +1,14 @@ +-- +-- The contents of this file are subject to the license and copyright +-- detailed in the LICENSE and NOTICE files at the root of the source +-- tree and available online at +-- +-- http://www.dspace.org/license/ +-- + +----------------------------------------------------------------------------------- +-- Alter TABLE cris_layout_box2securitygroup ADD alternative_box_id +----------------------------------------------------------------------------------- + +ALTER TABLE cris_layout_box2securitygroup ADD COLUMN alternative_box_id INTEGER; +ALTER TABLE cris_layout_box2securitygroup ADD CONSTRAINT cris_layout_box2securitygroup_box_id2 FOREIGN KEY (alternative_box_id) REFERENCES cris_layout_box (id) ON DELETE SET NULL; \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/builder/CrisLayoutBoxBuilder.java b/dspace-api/src/test/java/org/dspace/builder/CrisLayoutBoxBuilder.java index cf80c8778dce..59784b192ed4 100644 --- a/dspace-api/src/test/java/org/dspace/builder/CrisLayoutBoxBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/CrisLayoutBoxBuilder.java @@ -19,7 +19,9 @@ import org.dspace.content.MetadataField; import org.dspace.core.Context; import org.dspace.discovery.SearchServiceException; +import org.dspace.eperson.Group; import org.dspace.layout.CrisLayoutBox; +import org.dspace.layout.CrisLayoutBox2SecurityGroup; import org.dspace.layout.CrisLayoutBoxTypes; import org.dspace.layout.CrisLayoutField; import org.dspace.layout.LayoutSecurity; @@ -163,6 +165,17 @@ public CrisLayoutBoxBuilder addMetadataSecurityField(MetadataField field) { return this; } + public CrisLayoutBoxBuilder addBox2SecurityGroups(Group group, CrisLayoutBox alternativeBox) throws SQLException { + if (this.box.getBox2SecurityGroups() == null) { + this.box.setBox2SecurityGroups(new HashSet<>()); + } + this.box.getBox2SecurityGroups().add( + new CrisLayoutBox2SecurityGroup(new CrisLayoutBox2SecurityGroup.CrisLayoutBox2SecurityGroupId(box, group), + box, group, alternativeBox) + ); + return this; + } + public CrisLayoutBoxBuilder withContainer(boolean container) { this.box.setContainer(container); return this; diff --git a/dspace-api/src/test/java/org/dspace/builder/CrisLayoutTabBuilder.java b/dspace-api/src/test/java/org/dspace/builder/CrisLayoutTabBuilder.java index 312d04bb4ada..4736324f4d35 100644 --- a/dspace-api/src/test/java/org/dspace/builder/CrisLayoutTabBuilder.java +++ b/dspace-api/src/test/java/org/dspace/builder/CrisLayoutTabBuilder.java @@ -20,10 +20,12 @@ import org.dspace.content.MetadataField; import org.dspace.core.Context; import org.dspace.discovery.SearchServiceException; +import org.dspace.eperson.Group; import org.dspace.layout.CrisLayoutBox; import org.dspace.layout.CrisLayoutCell; import org.dspace.layout.CrisLayoutRow; import org.dspace.layout.CrisLayoutTab; +import org.dspace.layout.CrisLayoutTab2SecurityGroup; import org.dspace.layout.LayoutSecurity; import org.dspace.layout.service.CrisLayoutTabService; @@ -207,4 +209,15 @@ public CrisLayoutTabBuilder addMetadatasecurity(MetadataField metadataField) { this.tab.getMetadataSecurityFields().add(metadataField); return this; } + + public CrisLayoutTabBuilder addTab2SecurityGroups(Group group, CrisLayoutTab alternativeTab) { + if (this.tab.getTab2SecurityGroups() == null) { + this.tab.setTab2SecurityGroups(new HashSet<>()); + } + this.tab.getTab2SecurityGroups().add( + new CrisLayoutTab2SecurityGroup(new CrisLayoutTab2SecurityGroup.CrisLayoutTab2SecurityGroupId(tab, group), + tab, group, alternativeTab) + ); + return this; + } } diff --git a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CrisLayoutTabConverter.java b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CrisLayoutTabConverter.java index d15a04cf0108..2c94bd8a8f3f 100644 --- a/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CrisLayoutTabConverter.java +++ b/dspace-server-webapp/src/main/java/org/dspace/app/rest/converter/CrisLayoutTabConverter.java @@ -9,6 +9,7 @@ import java.sql.SQLException; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -27,11 +28,14 @@ import org.dspace.core.Context; import org.dspace.core.exception.SQLRuntimeException; import org.dspace.layout.CrisLayoutBox; +import org.dspace.layout.CrisLayoutBox2SecurityGroup; import org.dspace.layout.CrisLayoutCell; import org.dspace.layout.CrisLayoutRow; import org.dspace.layout.CrisLayoutTab; +import org.dspace.layout.CrisLayoutTab2SecurityGroup; import org.dspace.layout.LayoutSecurity; import org.dspace.layout.service.CrisLayoutBoxService; +import org.dspace.layout.service.CrisLayoutTabService; import org.dspace.services.RequestService; import org.dspace.util.UUIDUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -61,18 +65,47 @@ public class CrisLayoutTabConverter implements DSpaceConverter convertTab(tab, projection)) + .orElseGet(CrisLayoutTabRest::new); + } + + private boolean hasAccess(Item item, CrisLayoutTab tab) { + Context context = ContextUtil.obtainCurrentRequestContext(); + return crisLayoutTabService.hasAccess(context, tab, item); + } + + private CrisLayoutTab findAlternativeTab(CrisLayoutTab tab) { + return tab.getTab2SecurityGroups() + .stream() + .map(CrisLayoutTab2SecurityGroup::getAlternativeTab) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + + private CrisLayoutTabRest convertTab(CrisLayoutTab tab, Projection projection) { CrisLayoutTabRest rest = new CrisLayoutTabRest(); - rest.setId(model.getID()); - rest.setEntityType(model.getEntity().getLabel()); - rest.setCustomFilter(model.getCustomFilter()); - rest.setShortname(model.getShortName()); - rest.setHeader(model.getHeader()); - rest.setPriority(model.getPriority()); - rest.setSecurity(model.getSecurity()); - rest.setRows(convertRows(getScopeItem(), model.getRows(), projection)); - rest.setLeading(model.isLeading()); + rest.setId(tab.getID()); + rest.setEntityType(tab.getEntity().getLabel()); + rest.setCustomFilter(tab.getCustomFilter()); + rest.setShortname(tab.getShortName()); + rest.setHeader(tab.getHeader()); + rest.setPriority(tab.getPriority()); + rest.setSecurity(tab.getSecurity()); + rest.setRows(convertRows(getScopeItem(), tab.getRows(), projection)); + rest.setLeading(tab.isLeading()); return rest; } @@ -124,15 +157,43 @@ private CrisLayoutCellRest convertCell(Item item, CrisLayoutCell cell, Projectio private List convertBoxes(Item item, List boxes, Projection projection) { return boxes.stream() - .filter(box -> item == null || hasAccess(item, box)) - .map(box -> boxConverter.convert(box, projection)) - .collect(Collectors.toList()); + .map(box -> getCrisLayoutBox(item, box)) + .filter(Objects::nonNull) + .map(box -> boxConverter.convert(box, projection)) + .collect(Collectors.toList()); + } + + private CrisLayoutBox getCrisLayoutBox(Item item, CrisLayoutBox box) { + + if (item == null) { + return box; + } + + return Optional.of(box) + .filter(b -> hasAccess(item, b) && hasContent(item, b)) + .orElseGet(() -> + Optional.ofNullable(findAlternativeBox(box)) + .filter(altBox -> hasContent(item, altBox)) + .orElse(null)); } private boolean hasAccess(Item item, CrisLayoutBox box) { Context context = ContextUtil.obtainCurrentRequestContext(); - return crisLayoutBoxService.hasContent(context, box, item) - && crisLayoutBoxService.hasAccess(context, box, item); + return crisLayoutBoxService.hasAccess(context, box, item); + } + + private boolean hasContent(Item item, CrisLayoutBox box) { + Context context = ContextUtil.obtainCurrentRequestContext(); + return crisLayoutBoxService.hasContent(context, box, item); + } + + private CrisLayoutBox findAlternativeBox(CrisLayoutBox box) { + return box.getBox2SecurityGroups() + .stream() + .map(CrisLayoutBox2SecurityGroup::getAlternativeBox) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); } private CrisLayoutRow toRowModel(Context context, CrisLayoutRowRest rowRest) { diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/LayoutSecurityIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/LayoutSecurityIT.java index 5d293ce2fbc3..aa5705beb861 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/LayoutSecurityIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/LayoutSecurityIT.java @@ -53,6 +53,7 @@ import org.dspace.eperson.service.GroupService; import org.dspace.eperson.service.RegistrationDataService; import org.dspace.layout.CrisLayoutBox; +import org.dspace.layout.CrisLayoutBox2SecurityGroup; import org.dspace.layout.LayoutSecurity; import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; import org.hamcrest.Matchers; @@ -491,14 +492,20 @@ public void customDataTestWithOneGroup() throws Exception { .build(); // Create Group with member userA - Set groups = new HashSet<>(); + Set box2SecurityGroups = new HashSet<>(); Group testGroup = GroupBuilder.createGroup(context) .withName("testGroup") .addMember(userA) .build(); - groups.add(testGroup); - box1.setGroupSecurityFields(groups); + new CrisLayoutBox2SecurityGroup( + new CrisLayoutBox2SecurityGroup.CrisLayoutBox2SecurityGroupId(box1, testGroup), + box1, testGroup, null); + + box2SecurityGroups.add(new CrisLayoutBox2SecurityGroup( + new CrisLayoutBox2SecurityGroup.CrisLayoutBox2SecurityGroupId(box1, testGroup), + box1, testGroup, null)); + box1.setBox2SecurityGroups(box2SecurityGroups); CrisLayoutFieldBuilder.createMetadataField(context, abs, 0, 0) .withLabel("LABEL ABS") @@ -577,7 +584,7 @@ public void customDataTestWithMultipleGroup() throws Exception { .build(); // Create Group with member userA - Set boxGroups = new HashSet<>(); + Set boxGroups = new HashSet<>(); Group testGroup = GroupBuilder.createGroup(context) .withName("testGroup") @@ -589,9 +596,14 @@ public void customDataTestWithMultipleGroup() throws Exception { .addMember(userB) .build(); - boxGroups.add(testGroup); - boxGroups.add(testGroup1); - box1.setGroupSecurityFields(boxGroups); + boxGroups.add(new CrisLayoutBox2SecurityGroup( + new CrisLayoutBox2SecurityGroup.CrisLayoutBox2SecurityGroupId(box1, testGroup), + box1, testGroup, null)); + boxGroups.add(new CrisLayoutBox2SecurityGroup( + new CrisLayoutBox2SecurityGroup.CrisLayoutBox2SecurityGroupId(box1, testGroup1), + box1, testGroup, null)); + + box1.setBox2SecurityGroups(boxGroups); CrisLayoutFieldBuilder.createMetadataField(context, abs, 0, 0) .withLabel("LABEL ABS") diff --git a/dspace-server-webapp/src/test/java/org/dspace/app/rest/layout/CrisLayoutTabRestRepositoryIT.java b/dspace-server-webapp/src/test/java/org/dspace/app/rest/layout/CrisLayoutTabRestRepositoryIT.java index d536f547d0bd..aaa3fb1c62b6 100644 --- a/dspace-server-webapp/src/test/java/org/dspace/app/rest/layout/CrisLayoutTabRestRepositoryIT.java +++ b/dspace-server-webapp/src/test/java/org/dspace/app/rest/layout/CrisLayoutTabRestRepositoryIT.java @@ -77,6 +77,7 @@ import org.dspace.content.service.RelationshipService; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; +import org.dspace.eperson.service.GroupService; import org.dspace.layout.CrisLayoutBox; import org.dspace.layout.CrisLayoutBoxTypes; import org.dspace.layout.CrisLayoutCell; @@ -122,6 +123,9 @@ public class CrisLayoutTabRestRepositoryIT extends AbstractControllerIntegration @Autowired protected RelationshipService relationshipService; + @Autowired + protected GroupService groupService; + private final String METADATASECURITY_URL = "http://localhost:8080/api/core/metadatafield/"; /** @@ -2469,6 +2473,213 @@ public void excludeThumbnailNegativeMetadataValueMatcherTabMultiBoxConfiguration .andExpect(jsonPath("$._embedded.tabs[0].rows[1]").doesNotExist()); } + @Test + public void testFindByItemWithAlternativeTabs() throws Exception { + context.turnOffAuthorisationSystem(); + + MetadataSchema schema = mdss.find(context, "person"); + MetadataField firstName = mfss.findByElement(context, schema, "givenName", null); + Group adminGroup = groupService.findByName(context, Group.ADMIN); + // Create new community + Community community = CommunityBuilder.createCommunity(context) + .withName("Test Community") + .withTitle("Title test community") + .build(); + // Create new collection + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("Test Collection") + .build(); + // Create entity Type + EntityTypeBuilder.createEntityTypeBuilder(context, "Publication") + .build(); + EntityType eTypePer = EntityTypeBuilder.createEntityTypeBuilder(context, "Person") + .build(); + // Create new person item + Item item = ItemBuilder.createItem(context, collection) + .withPersonIdentifierFirstName("Danilo") + .withPersonIdentifierLastName("Di Nuzzo") + .withEntityType(eTypePer.getLabel()) + .build(); + + CrisLayoutBox boxOne = CrisLayoutBoxBuilder.createBuilder(context, eTypePer, false, false) + .withShortname("Box shortname 1") + .withSecurity(LayoutSecurity.PUBLIC) + .withContainer(false) + .build(); + + CrisLayoutBox boxTwo = CrisLayoutBoxBuilder.createBuilder(context, eTypePer, false, false) + .withShortname("Box shortname 2") + .withSecurity(LayoutSecurity.PUBLIC) + .withContainer(false) + .build(); + + CrisLayoutFieldBuilder.createMetadataField(context, firstName, 0, 1) + .withLabel("GIVEN NAME") + .withRendering("TEXT") + .withBox(boxOne) + .build(); + + CrisLayoutFieldBuilder.createMetadataField(context, firstName, 0, 1) + .withLabel("GIVEN NAME") + .withRendering("TEXT") + .withBox(boxTwo) + .build(); + + // add boxOne to tabOne + CrisLayoutTab tabOne = + CrisLayoutTabBuilder.createTab(context, eTypePer, 0) + .withShortName("TabOne For Person - priority 0") + .withSecurity(LayoutSecurity.ADMINISTRATOR) + .withHeader("New Tab header") + .addBoxIntoNewRow(boxOne, "rowTwoStyle", "cellOfRowTwoStyle") + .build(); + + // add boxTwo to tabTwo + CrisLayoutTab tabTwo = + CrisLayoutTabBuilder.createTab(context, eTypePer, 0) + .withShortName("Tab2 For Person - priority 0") + .withSecurity(LayoutSecurity.CUSTOM_DATA) + .withHeader("New Tab2 header") + .addBoxIntoNewRow(boxTwo, "rowTwoStyle2", "cellOfRowTwoStyle2") + .addTab2SecurityGroups(adminGroup, tabOne) + .build(); + + context.restoreAuthSystemState(); + + // admin user will see two tabs + getClient(getAuthToken(admin.getEmail(), password)) + .perform(get("/api/layout/tabs/search/findByItem") + .param("uuid", item.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", is(2))) + .andExpect(jsonPath("$._embedded.tabs[0].id", is(tabOne.getID()))) + .andExpect(jsonPath("$._embedded.tabs[0].shortname", is("TabOne For Person - priority 0"))) + .andExpect(jsonPath("$._embedded.tabs[0].header", is("New Tab header"))) + .andExpect(jsonPath("$._embedded.tabs[0].security", is(LayoutSecurity.ADMINISTRATOR.getValue()))) + .andExpect(jsonPath("$._embedded.tabs[0].rows", hasSize(1))) + .andExpect(jsonPath("$._embedded.tabs[1].id", is(tabTwo.getID()))) + .andExpect(jsonPath("$._embedded.tabs[1].shortname", is("Tab2 For Person - priority 0"))) + .andExpect(jsonPath("$._embedded.tabs[1].header", is("New Tab2 header"))) + .andExpect(jsonPath("$._embedded.tabs[1].security", is(LayoutSecurity.CUSTOM_DATA.getValue()))) + .andExpect(jsonPath("$._embedded.tabs[1].rows", hasSize(1))); + + // anonymous user will see only alternative tab is tabOne + getClient().perform(get("/api/layout/tabs/search/findByItem") + .param("uuid", item.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.page.totalElements", is(1))) + .andExpect(jsonPath("$._embedded.tabs[0].id", is(tabOne.getID()))) + .andExpect(jsonPath("$._embedded.tabs[0].shortname", is("TabOne For Person - priority 0"))) + .andExpect(jsonPath("$._embedded.tabs[0].header", is("New Tab header"))) + .andExpect(jsonPath("$._embedded.tabs[0].security", is(LayoutSecurity.ADMINISTRATOR.getValue()))) + .andExpect(jsonPath("$._embedded.tabs[0].rows", hasSize(1))) + .andExpect(jsonPath("$._embedded.tabs[0].rows[0].style", is("rowTwoStyle"))) + .andExpect(jsonPath("$._embedded.tabs[0].rows[0].cells", hasSize(1))) + .andExpect(jsonPath("$._embedded.tabs[0].rows[0].cells[0].style", is("cellOfRowTwoStyle"))) + .andExpect(jsonPath("$._embedded.tabs[0].rows[0].cells[0].boxes", contains(matchBox(boxOne)))); + } + + @Test + public void testFindByItemWithAlternativeBoxes() throws Exception { + context.turnOffAuthorisationSystem(); + + MetadataSchema schema = mdss.find(context, "person"); + MetadataField firstName = mfss.findByElement(context, schema, "givenName", null); + Group adminGroup = groupService.findByName(context, Group.ADMIN); + // Create new community + Community community = CommunityBuilder.createCommunity(context) + .withName("Test Community") + .withTitle("Title test community") + .build(); + // Create new collection + Collection collection = CollectionBuilder.createCollection(context, community) + .withName("Test Collection") + .build(); + // Create entity Type + EntityTypeBuilder.createEntityTypeBuilder(context, "Publication") + .build(); + EntityType eTypePer = EntityTypeBuilder.createEntityTypeBuilder(context, "Person") + .build(); + // Create new person item + Item item = ItemBuilder.createItem(context, collection) + .withPersonIdentifierFirstName("Danilo") + .withPersonIdentifierLastName("Di Nuzzo") + .withEntityType(eTypePer.getLabel()) + .build(); + + CrisLayoutBox boxOne = CrisLayoutBoxBuilder.createBuilder(context, eTypePer, false, false) + .withShortname("Box shortname 1") + .withSecurity(LayoutSecurity.PUBLIC) + .withContainer(false) + .build(); + + // add boxOne as alternative to boxTwo + CrisLayoutBox boxTwo = CrisLayoutBoxBuilder.createBuilder(context, eTypePer, false, false) + .withShortname("Box shortname 2") + .withSecurity(LayoutSecurity.CUSTOM_DATA) + .withContainer(false) + .addBox2SecurityGroups(adminGroup, boxOne) + .build(); + + CrisLayoutFieldBuilder.createMetadataField(context, firstName, 0, 1) + .withLabel("GIVEN NAME") + .withRendering("TEXT") + .withBox(boxOne) + .build(); + + CrisLayoutFieldBuilder.createMetadataField(context, firstName, 0, 1) + .withLabel("GIVEN NAME") + .withRendering("TEXT") + .withBox(boxTwo) + .build(); + + // add boxTwo to tab + CrisLayoutTab tab = CrisLayoutTabBuilder.createTab(context, eTypePer, 0) + .withShortName("TabOne For Person - priority 0") + .withSecurity(LayoutSecurity.PUBLIC) + .withHeader("New Tab header") + .withLeading(true) + .addBoxIntoNewRow(boxTwo, "rowTwoStyle", "cellOfRowTwoStyle") + .build(); + + context.restoreAuthSystemState(); + + // admin user will see boxTwo + getClient(getAuthToken(admin.getEmail(), password)) + .perform(get("/api/layout/tabs/search/findByItem") + .param("uuid", item.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.tabs[0].id", is(tab.getID()))) + .andExpect(jsonPath("$._embedded.tabs[0].shortname", is("TabOne For Person - priority 0"))) + .andExpect(jsonPath("$._embedded.tabs[0].header", is("New Tab header"))) + .andExpect(jsonPath("$._embedded.tabs[0].leading", is(true))) + .andExpect(jsonPath("$._embedded.tabs[0].security", is(LayoutSecurity.PUBLIC.getValue()))) + .andExpect(jsonPath("$._embedded.tabs[0].rows", hasSize(1))) + .andExpect(jsonPath("$._embedded.tabs[0].rows[0].style", is("rowTwoStyle"))) + .andExpect(jsonPath("$._embedded.tabs[0].rows[0].cells", hasSize(1))) + .andExpect(jsonPath("$._embedded.tabs[0].rows[0].cells[0].style", is("cellOfRowTwoStyle"))) + .andExpect(jsonPath("$._embedded.tabs[0].rows[0].cells[0].boxes", contains(matchBox(boxTwo)))); + + // anonymous user will see boxOne + getClient().perform(get("/api/layout/tabs/search/findByItem") + .param("uuid", item.getID().toString())) + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$._embedded.tabs[0].id", is(tab.getID()))) + .andExpect(jsonPath("$._embedded.tabs[0].shortname", is("TabOne For Person - priority 0"))) + .andExpect(jsonPath("$._embedded.tabs[0].header", is("New Tab header"))) + .andExpect(jsonPath("$._embedded.tabs[0].leading", is(true))) + .andExpect(jsonPath("$._embedded.tabs[0].security", is(LayoutSecurity.PUBLIC.getValue()))) + .andExpect(jsonPath("$._embedded.tabs[0].rows", hasSize(1))) + .andExpect(jsonPath("$._embedded.tabs[0].rows[0].style", is("rowTwoStyle"))) + .andExpect(jsonPath("$._embedded.tabs[0].rows[0].cells", hasSize(1))) + .andExpect(jsonPath("$._embedded.tabs[0].rows[0].cells[0].style", is("cellOfRowTwoStyle"))) + .andExpect(jsonPath("$._embedded.tabs[0].rows[0].cells[0].boxes", contains(matchBox(boxOne)))); + } + private CrisLayoutTabRest parseJson(String name) throws Exception { return new ObjectMapper().readValue(getFileInputStream(name), CrisLayoutTabRest.class); } diff --git a/dspace/config/hibernate.cfg.xml b/dspace/config/hibernate.cfg.xml index 8597bedbc34a..9a3cdd967f0f 100644 --- a/dspace/config/hibernate.cfg.xml +++ b/dspace/config/hibernate.cfg.xml @@ -109,6 +109,8 @@ + + diff --git a/dspace/etc/conftool/cris-layout-configuration.xls b/dspace/etc/conftool/cris-layout-configuration.xls index 6a7d9daf8e80..d9c9ab9f0090 100644 Binary files a/dspace/etc/conftool/cris-layout-configuration.xls and b/dspace/etc/conftool/cris-layout-configuration.xls differ