diff --git a/README.md b/README.md index ec8ca078f97dbe16a353f49524e78c9fa840f302..82983d7d9934f2f2429252b0110872375a2c40c5 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,12 @@ The first super admin user must be added manually, then he/she will be able to a The value `user_id` is the RAP user id. +## Hooks + +It is possible to load external jar files in order to extend the GMS with custom functionalities (implementing the *Hook interfaces and annotating the class with `@org.springframework.stereotype.Component`). Currently only the `GroupsHook` is available. + +External jar files need to be specified at application startup using the `LOADER_PATH` environment variable (this is a Spring feature). + ## Developer notes Backend and frontend are 2 separate applications: diff --git a/gms/pom.xml b/gms/pom.xml index d3842dae4a1961cfcd345115d5519cd4de8bc45a..47d61e3a46cfc29fdfb27805792ebb1e8fbfdfca 100644 --- a/gms/pom.xml +++ b/gms/pom.xml @@ -114,6 +114,26 @@ </execution> </executions> </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <version>3.1.2</version> + <executions> + <execution> + <goals> + <goal>jar</goal> + </goals> + <phase>package</phase> + <configuration> + <!-- creates a gms-lib jar that can be included as dependency in other projects --> + <classifier>lib</classifier> + <includes> + <include>**/**</include> + </includes> + </configuration> + </execution> + </executions> + </plugin> <plugin> <groupId>com.spotify</groupId> <artifactId>dockerfile-maven-plugin</artifactId> @@ -130,6 +150,8 @@ <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <executable>true</executable> + <!-- layout ZIP is necessary for loading external jar using PropertiesLauncher (used for hooks) --> + <layout>ZIP</layout> </configuration> </plugin> <plugin> diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java index fc11b090313df3acddc37608b0ed7ceb58601158..64f838d69b041db31b4fff84d054e0b762415255 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java +++ b/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java @@ -193,7 +193,7 @@ public class JWTWebServiceController { @DeleteMapping(value = "/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE) public void deleteGroup(@PathVariable("group") String groupParam, HttpServletResponse response) { GroupEntity group = getGroupFromNames(extractGroupNames(groupParam)); - groupsDAO.deleteGroupById(group.getId()); + groupsDAO.deleteGroup(group); response.setStatus(HttpServletResponse.SC_NO_CONTENT); } diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java index 301ebd8cbb61e7165bd5d24ab721be0c39ba89f9..42285935cb9a1324dd87212b566315ce08b8c509 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java +++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java @@ -2,6 +2,7 @@ package it.inaf.ia2.gms.persistence; import it.inaf.ia2.gms.model.GroupBreadcrumb; import it.inaf.ia2.gms.persistence.model.GroupEntity; +import it.inaf.ia2.gms.service.hook.GroupsHook; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -21,6 +22,9 @@ import org.springframework.stereotype.Component; @Component public class GroupsDAO { + @Autowired(required = false) + protected GroupsHook groupsHook; + private final JdbcTemplate jdbcTemplate; @Autowired @@ -30,6 +34,10 @@ public class GroupsDAO { public GroupEntity createGroup(GroupEntity group) { + if (groupsHook != null) { + groupsHook.beforeCreate(group); + } + String sql = "INSERT INTO gms_group (id, name, path, is_leaf) VALUES (?, ?, ?, ?)"; jdbcTemplate.update(conn -> { @@ -46,6 +54,10 @@ public class GroupsDAO { public GroupEntity updateGroup(GroupEntity group) { + if (groupsHook != null) { + groupsHook.beforeUpdate(group); + } + String sql = "UPDATE gms_group SET name = ?, path = ?, is_leaf = ? WHERE id = ?"; jdbcTemplate.update(conn -> { @@ -60,9 +72,14 @@ public class GroupsDAO { return group; } - public void deleteGroupById(String groupId) { + public void deleteGroup(GroupEntity group) { + + if (groupsHook != null) { + groupsHook.beforeDelete(group); + } + String sql = "DELETE FROM gms_group WHERE id = ?"; - jdbcTemplate.update(sql, groupId); + jdbcTemplate.update(sql, group.getId()); } public Optional<GroupEntity> findGroupById(String groupId) { diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/GroupNameService.java b/gms/src/main/java/it/inaf/ia2/gms/service/GroupNameService.java index 0cb329b6d85acd5e9f44f52af65d90ff7e76178e..5668fb21a63083f43acd0718666cb8f7d7a18242 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/service/GroupNameService.java +++ b/gms/src/main/java/it/inaf/ia2/gms/service/GroupNameService.java @@ -3,6 +3,7 @@ package it.inaf.ia2.gms.service; import it.inaf.ia2.gms.persistence.GroupsDAO; import it.inaf.ia2.gms.persistence.model.GroupEntity; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -26,6 +27,10 @@ public class GroupNameService { return getGroupsNames(groupsDAO.findGroupsByIds(groupIdentifiers)); } + public String getGroupCompleteName(GroupEntity group) { + return getGroupsNames(Collections.singletonList(group)).get(0); + } + /** * Returns the list of the group complete names, given a list of GroupEntity * objects. diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java index d6416c95c33fec735675bfd408feeb18baa2c7c0..6ae23bbeab2576d173fcd0757978f03845526bdf 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java +++ b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java @@ -108,8 +108,8 @@ public class GroupsService { membershipsDAO.deleteAllGroupsMembership(groupsToDeleteIds); permissionsDAO.deleteAllGroupsPermissions(groupsToDeleteIds); - for (String groupId : groupsToDeleteIds) { - groupsDAO.deleteGroupById(groupId); + for (GroupEntity g : groupsToDelete) { + groupsDAO.deleteGroup(g); } loggingDAO.logAction("Group deleted, group_id=" + group.getId()); diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/hook/GroupsHook.java b/gms/src/main/java/it/inaf/ia2/gms/service/hook/GroupsHook.java new file mode 100644 index 0000000000000000000000000000000000000000..9f73f091bbd63d6244cff39912a10178afeea250 --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/service/hook/GroupsHook.java @@ -0,0 +1,12 @@ +package it.inaf.ia2.gms.service.hook; + +import it.inaf.ia2.gms.persistence.model.GroupEntity; + +public interface GroupsHook { + + void beforeCreate(GroupEntity group); + + void beforeUpdate(GroupEntity group); + + void beforeDelete(GroupEntity group); +} diff --git a/gms/src/test/java/it/inaf/ia2/gms/HooksConfig.java b/gms/src/test/java/it/inaf/ia2/gms/HooksConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..6e9e02f747aa8d59db418e130935765ba5bd12c2 --- /dev/null +++ b/gms/src/test/java/it/inaf/ia2/gms/HooksConfig.java @@ -0,0 +1,30 @@ +package it.inaf.ia2.gms; + +import it.inaf.ia2.gms.persistence.model.GroupEntity; +import it.inaf.ia2.gms.service.hook.GroupsHook; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class HooksConfig { + + @Bean + public GroupsHook groupsHook() { + return new GroupsHookTestImpl(); + } + + public static class GroupsHookTestImpl implements GroupsHook { + + @Override + public void beforeCreate(GroupEntity group) { + } + + @Override + public void beforeUpdate(GroupEntity group) { + } + + @Override + public void beforeDelete(GroupEntity group) { + } + } +} diff --git a/gms/src/test/java/it/inaf/ia2/gms/controller/JWTWebServiceControllerTest.java b/gms/src/test/java/it/inaf/ia2/gms/controller/JWTWebServiceControllerTest.java index 5bf8234f6e66d079a85e7ed6c19ae823261c1f3d..625e72c14dc3f3feb6113d24a72e2a309fe791c4 100644 --- a/gms/src/test/java/it/inaf/ia2/gms/controller/JWTWebServiceControllerTest.java +++ b/gms/src/test/java/it/inaf/ia2/gms/controller/JWTWebServiceControllerTest.java @@ -151,7 +151,7 @@ public class JWTWebServiceControllerTest { mockMvc.perform(delete("/ws/jwt/LBT.INAF")) .andExpect(status().isNoContent()); - verify(groupsDAO, times(1)).deleteGroupById(eq(inaf.getId())); + verify(groupsDAO, times(1)).deleteGroup(eq(inaf)); } @Test diff --git a/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsDAOTest.java b/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsDAOTest.java index 271779ebbda4867291013750c1e22743ef377189..fd56e8a6302bab237d6d0ffb69930f77e8a59dd7 100644 --- a/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsDAOTest.java +++ b/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsDAOTest.java @@ -1,8 +1,10 @@ package it.inaf.ia2.gms.persistence; import it.inaf.ia2.gms.DataSourceConfig; +import it.inaf.ia2.gms.HooksConfig; import it.inaf.ia2.gms.model.GroupBreadcrumb; import it.inaf.ia2.gms.persistence.model.GroupEntity; +import it.inaf.ia2.gms.service.hook.GroupsHook; import java.util.List; import java.util.Map; import java.util.Optional; @@ -14,23 +16,32 @@ import static org.junit.Assert.assertTrue; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import org.mockito.internal.util.collections.Sets; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) -@ContextConfiguration(classes = DataSourceConfig.class) +@ContextConfiguration(classes = {DataSourceConfig.class, HooksConfig.class}) public class GroupsDAOTest { @Autowired private DataSource dataSource; + @Autowired + @SpyBean + private GroupsHook groupsHook; + private GroupsDAO dao; @Before public void setUp() { dao = new GroupsDAO(dataSource); + dao.groupsHook = groupsHook; } @Test @@ -43,6 +54,8 @@ public class GroupsDAOTest { root.setPath(""); dao.createGroup(root); + verify(groupsHook, times(1)).beforeCreate(eq(root)); + GroupEntity lbt = new GroupEntity(); lbt.setId(getNewGroupId()); lbt.setName("LBT"); @@ -118,6 +131,8 @@ public class GroupsDAOTest { tng = dao.findGroupById(tng.getId()).get(); assertEquals(newName, tng.getName()); + verify(groupsHook, times(1)).beforeUpdate(eq(tng)); + // Breadcrumbs List<GroupBreadcrumb> breadcrumbs = dao.getBreadcrumbs(lbt.getId() + "." + lbtInaf.getId()); assertEquals(3, breadcrumbs.size()); @@ -126,9 +141,11 @@ public class GroupsDAOTest { assertEquals(lbtInaf.getName(), breadcrumbs.get(2).getGroupName()); // Delete - dao.deleteGroupById(lbtInaf.getId()); + dao.deleteGroup(lbtInaf); groups = dao.getDirectSubGroups(lbt.getId()); assertTrue(groups.isEmpty()); + + verify(groupsHook, times(1)).beforeDelete(eq(lbtInaf)); } private String getNewGroupId() {