diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/AclDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/AclDao.java index 7ed02650..8d32d2e9 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/AclDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/AclDao.java @@ -1,7 +1,6 @@ package com.sismics.docs.core.dao.jpa; import com.sismics.docs.core.constant.AclTargetType; -import com.sismics.docs.core.constant.AclType; import com.sismics.docs.core.constant.AuditLogType; import com.sismics.docs.core.constant.PermType; import com.sismics.docs.core.dao.jpa.dto.AclDto; @@ -65,7 +64,7 @@ public class AclDao { * @return ACL DTO list */ @SuppressWarnings("unchecked") - public List getBySourceId(String sourceId, AclType type) { + public List getBySourceId(String sourceId) { EntityManager em = ThreadLocalContext.get().getEntityManager(); StringBuilder sb = new StringBuilder("select a.ACL_ID_C, a.ACL_PERM_C, a.ACL_TARGETID_C, "); sb.append(" u.USE_USERNAME_C, s.SHA_ID_C, s.SHA_NAME_C, g.GRP_NAME_C "); @@ -73,12 +72,11 @@ public class AclDao { sb.append(" left join T_USER u on u.USE_ID_C = a.ACL_TARGETID_C "); sb.append(" left join T_SHARE s on s.SHA_ID_C = a.ACL_TARGETID_C "); sb.append(" left join T_GROUP g on g.GRP_ID_C = a.ACL_TARGETID_C "); - sb.append(" where a.ACL_DELETEDATE_D is null and a.ACL_SOURCEID_C = :sourceId and a.ACL_TYPE_C = :type "); + sb.append(" where a.ACL_DELETEDATE_D is null and a.ACL_SOURCEID_C = :sourceId "); // Perform the query Query q = em.createNativeQuery(sb.toString()); q.setParameter("sourceId", sourceId); - q.setParameter("type", type.name()); List l = q.getResultList(); // Assemble results @@ -142,29 +140,26 @@ public class AclDao { * @param perm Permission * @param targetId Target ID * @param userId User ID - * @param type Type */ @SuppressWarnings("unchecked") - public void delete(String sourceId, PermType perm, String targetId, String userId, AclType type) { + public void delete(String sourceId, PermType perm, String targetId, String userId) { EntityManager em = ThreadLocalContext.get().getEntityManager(); // Create audit log - Query q = em.createQuery("from Acl a where a.sourceId = :sourceId and a.perm = :perm and a.targetId = :targetId and a.type = :type"); + Query q = em.createQuery("from Acl a where a.sourceId = :sourceId and a.perm = :perm and a.targetId = :targetId"); q.setParameter("sourceId", sourceId); q.setParameter("perm", perm); q.setParameter("targetId", targetId); - q.setParameter("type", type); List aclList = q.getResultList(); for (Acl acl : aclList) { AuditLogUtil.create(acl, AuditLogType.DELETE, userId); } // Soft delete the ACLs - q = em.createQuery("update Acl a set a.deleteDate = :dateNow where a.sourceId = :sourceId and a.perm = :perm and a.targetId = :targetId and a.type = :type"); + q = em.createQuery("update Acl a set a.deleteDate = :dateNow where a.sourceId = :sourceId and a.perm = :perm and a.targetId = :targetId"); q.setParameter("sourceId", sourceId); q.setParameter("perm", perm); q.setParameter("targetId", targetId); - q.setParameter("type", type); q.setParameter("dateNow", new Date()); q.executeUpdate(); } diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/RouteDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/RouteDao.java index 569de7d0..f202b58f 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/RouteDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/RouteDao.java @@ -6,9 +6,7 @@ import com.sismics.docs.core.util.AuditLogUtil; import com.sismics.util.context.ThreadLocalContext; import javax.persistence.EntityManager; -import javax.persistence.Query; import java.util.Date; -import java.util.List; import java.util.UUID; /** @@ -38,12 +36,4 @@ public class RouteDao { return route.getId(); } - - @SuppressWarnings("unchecked") - public List getActiveRoutes(String documentId) { - EntityManager em = ThreadLocalContext.get().getEntityManager(); - Query q = em.createQuery("from Route r where r.documentId = :documentId and r.deleteDate is null order by r.createDate desc"); - q.setParameter("documentId", documentId); - return q.getResultList(); - } } diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/RouteStepDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/RouteStepDao.java index 1b974240..c1ff79b2 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/RouteStepDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/RouteStepDao.java @@ -1,10 +1,14 @@ package com.sismics.docs.core.dao.jpa; +import com.sismics.docs.core.constant.RouteStepTransition; +import com.sismics.docs.core.constant.RouteStepType; +import com.sismics.docs.core.dao.jpa.dto.RouteStepDto; import com.sismics.docs.core.model.jpa.RouteStep; import com.sismics.util.context.ThreadLocalContext; import javax.persistence.EntityManager; import javax.persistence.Query; +import java.sql.Timestamp; import java.util.Date; import java.util.List; import java.util.UUID; @@ -33,11 +37,63 @@ public class RouteStepDao { return routeStep.getId(); } + /** + * Get the current route step from a document. + * + * @param documentId Document ID + * @return Current route step + */ @SuppressWarnings("unchecked") - public List getRouteSteps(String routeId) { + public RouteStepDto getCurrentStep(String documentId) { EntityManager em = ThreadLocalContext.get().getEntityManager(); - Query q = em.createQuery("from RouteStep r where r.routeId = :routeId and r.deleteDate is null order by r.order asc"); - q.setParameter("routeId", routeId); - return q.getResultList(); + StringBuilder sb = new StringBuilder("select rs.RTP_ID_C, rs.RTP_NAME_C, rs.RTP_TYPE_C, rs.RTP_TRANSITION_C, rs.RTP_COMMENT_C, rs.RTP_IDTARGET_C, rs.RTP_ENDDATE_D"); + sb.append(" from T_ROUTE_STEP rs "); + sb.append(" join T_ROUTE r on r.RTE_ID_C = rs.RTP_IDROUTE_C "); + sb.append(" where r.RTE_IDDOCUMENT_C = :documentId and rs.RTP_ENDDATE_D is null "); + sb.append(" order by rs.RTP_ORDER_N asc "); + + Query q = em.createNativeQuery(sb.toString()); + q.setParameter("documentId", documentId); + + List l = q.getResultList(); + if (l.isEmpty()) { + return null; + } + Object[] o = l.get(0); + int i = 0; + RouteStepDto routeStepDto = new RouteStepDto(); + routeStepDto.setId((String) o[i++]); + routeStepDto.setName((String) o[i++]); + routeStepDto.setType(RouteStepType.valueOf((String) o[i++])); + String transition = (String) o[i++]; + routeStepDto.setTransition(transition == null ? null : RouteStepTransition.valueOf(transition)); + routeStepDto.setComment((String) o[i++]); + routeStepDto.setTargetId((String) o[i++]); + Timestamp endDateTimestamp = (Timestamp) o[i]; + routeStepDto.setEndDateTimestamp(endDateTimestamp == null ? null : endDateTimestamp.getTime()); + return routeStepDto; + } + + /** + * End a route step. + * + * @param id Route step ID + * @param transition Transition + * @param comment Comment + * @param validatorUserId Validator user ID + */ + public void endRouteStep(String id, RouteStepTransition transition, String comment, String validatorUserId) { + StringBuilder sb = new StringBuilder("update T_ROUTE_STEP r "); + sb.append(" set r.RTP_ENDDATE_D = :endDate, r.RTP_TRANSITION_C = :transition, r.RTP_COMMENT_C = :comment, r.RTP_IDVALIDATORUSER_C = :validatorUserId "); + sb.append(" where r.RTP_ID_C = :id"); + + EntityManager em = ThreadLocalContext.get().getEntityManager(); + Query q = em.createNativeQuery(sb.toString()); + q.setParameter("endDate", new Date()); + q.setParameter("transition", transition.name()); + q.setParameter("comment", comment); + q.setParameter("validatorUserId", validatorUserId); + q.setParameter("id", id); + q.executeUpdate(); } } diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/UserDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/UserDao.java index 2e25c35b..4d7924fb 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/UserDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/UserDao.java @@ -121,7 +121,7 @@ public class UserDao { User userDb = (User) q.getSingleResult(); // Update the user - userDb.setStorageQuota(user.getStorageQuota()); + userDb.setStorageCurrent(user.getStorageCurrent()); } /** diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/RouteStepDto.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/RouteStepDto.java new file mode 100644 index 00000000..d665a949 --- /dev/null +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/RouteStepDto.java @@ -0,0 +1,109 @@ +package com.sismics.docs.core.dao.jpa.dto; + +import com.sismics.docs.core.constant.RouteStepTransition; +import com.sismics.docs.core.constant.RouteStepType; + +/** + * Route step DTO. + * + * @author bgamard + */ +public class RouteStepDto { + /** + * Route step ID. + */ + private String id; + + /** + * Name. + */ + private String name; + + /** + * Type. + */ + private RouteStepType type; + + /** + * Transition. + */ + private RouteStepTransition transition; + + /** + * Comment. + */ + private String comment; + + /** + * Target ID (user or group). + */ + private String targetId; + + /** + * End date. + */ + private Long endDateTimestamp; + + public String getId() { + return id; + } + + public RouteStepDto setId(String id) { + this.id = id; + return this; + } + + public String getName() { + return name; + } + + public RouteStepDto setName(String name) { + this.name = name; + return this; + } + + public RouteStepType getType() { + return type; + } + + public RouteStepDto setType(RouteStepType type) { + this.type = type; + return this; + } + + public RouteStepTransition getTransition() { + return transition; + } + + public RouteStepDto setTransition(RouteStepTransition transition) { + this.transition = transition; + return this; + } + + public String getComment() { + return comment; + } + + public RouteStepDto setComment(String comment) { + this.comment = comment; + return this; + } + + public String getTargetId() { + return targetId; + } + + public RouteStepDto setTargetId(String targetId) { + this.targetId = targetId; + return this; + } + + public Long getEndDateTimestamp() { + return endDateTimestamp; + } + + public RouteStepDto setEndDateTimestamp(Long endDateTimestamp) { + this.endDateTimestamp = endDateTimestamp; + return this; + } +} diff --git a/docs-core/src/main/java/com/sismics/docs/core/listener/async/DocumentUpdatedAsyncListener.java b/docs-core/src/main/java/com/sismics/docs/core/listener/async/DocumentUpdatedAsyncListener.java index 5df6b41a..2d208970 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/listener/async/DocumentUpdatedAsyncListener.java +++ b/docs-core/src/main/java/com/sismics/docs/core/listener/async/DocumentUpdatedAsyncListener.java @@ -1,10 +1,5 @@ package com.sismics.docs.core.listener.async; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.google.common.eventbus.Subscribe; import com.sismics.docs.core.dao.jpa.ContributorDao; import com.sismics.docs.core.dao.jpa.DocumentDao; @@ -12,6 +7,10 @@ import com.sismics.docs.core.dao.lucene.LuceneDao; import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent; import com.sismics.docs.core.model.jpa.Contributor; import com.sismics.docs.core.util.TransactionUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; /** * Listener on document updated. @@ -31,7 +30,7 @@ public class DocumentUpdatedAsyncListener { * @throws Exception */ @Subscribe - public void on(final DocumentUpdatedAsyncEvent event) throws Exception { + public void on(final DocumentUpdatedAsyncEvent event) { if (log.isInfoEnabled()) { log.info("Document updated event: " + event.toString()); } diff --git a/docs-core/src/main/java/com/sismics/docs/core/model/jpa/RouteStep.java b/docs-core/src/main/java/com/sismics/docs/core/model/jpa/RouteStep.java index c1a6b477..2207597d 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/model/jpa/RouteStep.java +++ b/docs-core/src/main/java/com/sismics/docs/core/model/jpa/RouteStep.java @@ -60,6 +60,12 @@ public class RouteStep { @Column(name = "RTP_IDTARGET_C", nullable = false, length = 36) private String targetId; + /** + * Validator user ID. + */ + @Column(name = "RTP_IDVALIDATORUSER_C", length = 36) + private String validatorUserId; + /** * Order. */ @@ -156,6 +162,15 @@ public class RouteStep { return this; } + public String getValidatorUserId() { + return validatorUserId; + } + + public RouteStep setValidatorUserId(String validatorUserId) { + this.validatorUserId = validatorUserId; + return this; + } + public Date getCreateDate() { return createDate; } diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/RoutingUtil.java b/docs-core/src/main/java/com/sismics/docs/core/util/RoutingUtil.java index 3c4cdf99..0ea6972b 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/util/RoutingUtil.java +++ b/docs-core/src/main/java/com/sismics/docs/core/util/RoutingUtil.java @@ -3,13 +3,8 @@ package com.sismics.docs.core.util; import com.sismics.docs.core.constant.AclType; import com.sismics.docs.core.constant.PermType; import com.sismics.docs.core.dao.jpa.AclDao; -import com.sismics.docs.core.dao.jpa.RouteDao; -import com.sismics.docs.core.dao.jpa.RouteStepDao; +import com.sismics.docs.core.dao.jpa.dto.RouteStepDto; import com.sismics.docs.core.model.jpa.Acl; -import com.sismics.docs.core.model.jpa.Route; -import com.sismics.docs.core.model.jpa.RouteStep; - -import java.util.List; /** * Routing utilities. @@ -17,31 +12,6 @@ import java.util.List; * @author bgamard */ public class RoutingUtil { - /** - * Get the current route step from a document. - * - * @param documentId Document ID - * @return Active route step - */ - public static RouteStep getCurrentStep(String documentId) { - // TODO Optimize - RouteDao routeDao = new RouteDao(); - List routeList = routeDao.getActiveRoutes(documentId); - if (routeList.isEmpty()) { - return null; - } - - Route route = routeList.get(0); - RouteStepDao routeStepDao = new RouteStepDao(); - List routeStepList = routeStepDao.getRouteSteps(route.getId()); - for (RouteStep routeStep : routeStepList) { - if (routeStep.getEndDate() == null) { - return routeStep; - } - } - - return null; - } /** * Update routing ACLs according to the current route step. @@ -51,7 +21,7 @@ public class RoutingUtil { * @param previousStep Previous route step * @param userId User ID */ - public static void updateAcl(String sourceId, RouteStep currentStep, RouteStep previousStep, String userId) { + public static void updateAcl(String sourceId, RouteStepDto currentStep, RouteStepDto previousStep, String userId) { AclDao aclDao = new AclDao(); if (previousStep != null) { @@ -59,12 +29,14 @@ public class RoutingUtil { aclDao.delete(sourceId, PermType.READ, previousStep.getTargetId(), userId, AclType.ROUTING); } - // Create a temporary READ ACL - Acl acl = new Acl(); - acl.setPerm(PermType.READ); - acl.setType(AclType.ROUTING); - acl.setSourceId(sourceId); - acl.setTargetId(currentStep.getTargetId()); - aclDao.create(acl, userId); + if (currentStep != null) { + // Create a temporary READ ACL + Acl acl = new Acl(); + acl.setPerm(PermType.READ); + acl.setType(AclType.ROUTING); + acl.setSourceId(sourceId); + acl.setTargetId(currentStep.getTargetId()); + aclDao.create(acl, userId); + } } } diff --git a/docs-core/src/main/java/com/sismics/util/context/ThreadLocalContext.java b/docs-core/src/main/java/com/sismics/util/context/ThreadLocalContext.java index 5d0049be..2201f176 100644 --- a/docs-core/src/main/java/com/sismics/util/context/ThreadLocalContext.java +++ b/docs-core/src/main/java/com/sismics/util/context/ThreadLocalContext.java @@ -71,6 +71,11 @@ public class ThreadLocalContext { * @return entityManager */ public EntityManager getEntityManager() { + if (entityManager != null && entityManager.isOpen()) { + // This disables the L1 cache + entityManager.flush(); + entityManager.clear(); + } return entityManager; } diff --git a/docs-core/src/main/resources/db/update/dbupdate-015-0.sql b/docs-core/src/main/resources/db/update/dbupdate-015-0.sql index 82210515..fe67f85a 100644 --- a/docs-core/src/main/resources/db/update/dbupdate-015-0.sql +++ b/docs-core/src/main/resources/db/update/dbupdate-015-0.sql @@ -1,9 +1,10 @@ create table T_ROUTE_MODEL ( RTM_ID_C varchar(36) not null, RTM_NAME_C varchar(50) not null, RTM_STEPS_C varchar(5000) not null, RTM_CREATEDATE_D datetime not null, RTM_DELETEDATE_D datetime, primary key (RTM_ID_C) ); create cached table T_ROUTE ( RTE_ID_C varchar(36) not null, RTE_IDDOCUMENT_C varchar(36) not null, RTE_NAME_C varchar(50) not null, RTE_CREATEDATE_D datetime not null, RTE_DELETEDATE_D datetime, primary key (RTE_ID_C) ); -create cached table T_ROUTE_STEP ( RTP_ID_C varchar(36) not null, RTP_IDROUTE_C varchar(36) not null, RTP_NAME_C varchar(200) not null, RTP_TYPE_C varchar(50) not null, RTP_TRANSITION_C varchar(50), RTP_COMMENT_C varchar(500), RTP_IDTARGET_C varchar(36) not null, RTP_ORDER_N int not null, RTP_CREATEDATE_D datetime not null, RTP_ENDDATE_D datetime, RTP_DELETEDATE_D datetime, primary key (RTP_ID_C) ); +create cached table T_ROUTE_STEP ( RTP_ID_C varchar(36) not null, RTP_IDROUTE_C varchar(36) not null, RTP_NAME_C varchar(200) not null, RTP_TYPE_C varchar(50) not null, RTP_TRANSITION_C varchar(50), RTP_COMMENT_C varchar(500), RTP_IDTARGET_C varchar(36) not null, RTP_IDVALIDATORUSER_C varchar(36), RTP_ORDER_N int not null, RTP_CREATEDATE_D datetime not null, RTP_ENDDATE_D datetime, RTP_DELETEDATE_D datetime, primary key (RTP_ID_C) ); alter table T_ACL add column ACL_TYPE_C varchar(30) not null default 'USER'; alter table T_ROUTE add constraint FK_RTE_IDDOCUMENT_C foreign key (RTE_IDDOCUMENT_C) references T_DOCUMENT (DOC_ID_C) on delete restrict on update restrict; alter table T_ROUTE_STEP add constraint FK_RTP_IDROUTE_C foreign key (RTP_IDROUTE_C) references T_ROUTE (RTE_ID_C) on delete restrict on update restrict; +alter table T_ROUTE_STEP add constraint FK_RTP_IDVALIDATORUSER_C foreign key (RTP_IDVALIDATORUSER_C) references T_USER (USE_ID_C) on delete restrict on update restrict; insert into T_ROUTE_MODEL (RTM_ID_C, RTM_NAME_C, RTM_STEPS_C, RTM_CREATEDATE_D) values ('default-document-review', 'Document review', '[{"type":"VALIDATE","target":{"name":"administrators","type":"GROUP"},"name":"Check the document''s metadata"},{"type":"VALIDATE","target":{"name":"administrators","type":"GROUP"},"name":"Add relevant files to the document"},{"type":"APPROVE","target":{"name":"administrators","type":"GROUP"},"name":"Approve the document"}]', now()); diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java index dfab01fe..2e5e4669 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java @@ -13,9 +13,11 @@ import com.sismics.docs.core.event.DocumentCreatedAsyncEvent; import com.sismics.docs.core.event.DocumentDeletedAsyncEvent; import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent; import com.sismics.docs.core.event.FileDeletedAsyncEvent; -import com.sismics.docs.core.model.jpa.*; +import com.sismics.docs.core.model.jpa.Acl; +import com.sismics.docs.core.model.jpa.Document; +import com.sismics.docs.core.model.jpa.File; +import com.sismics.docs.core.model.jpa.User; import com.sismics.docs.core.util.PdfUtil; -import com.sismics.docs.core.util.RoutingUtil; import com.sismics.docs.core.util.jpa.PaginatedList; import com.sismics.docs.core.util.jpa.PaginatedLists; import com.sismics.docs.core.util.jpa.SortCriteria; @@ -212,12 +214,12 @@ public class DocumentResource extends BaseResource { document.add("relations", relationList); // Add current route step - RouteStep routeStep = RoutingUtil.getCurrentStep(documentId); - if (routeStep != null && !principal.isAnonymous()) { + RouteStepDto routeStepDto = new RouteStepDao().getCurrentStep(documentId); + if (routeStepDto != null && !principal.isAnonymous()) { document.add("route_step", Json.createObjectBuilder() - .add("name", routeStep.getName()) - .add("type", routeStep.getType().name()) - .add("transitionable", getTargetIdList(null).contains(routeStep.getTargetId()))); + .add("name", routeStepDto.getName()) + .add("type", routeStepDto.getType().name()) + .add("transitionable", getTargetIdList(null).contains(routeStepDto.getTargetId()))); } return Response.ok().entity(document.build()).build(); diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/RouteResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/RouteResource.java index 379352e5..2781fc4e 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/RouteResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/RouteResource.java @@ -2,11 +2,13 @@ package com.sismics.docs.rest.resource; import com.sismics.docs.core.constant.AclTargetType; import com.sismics.docs.core.constant.PermType; +import com.sismics.docs.core.constant.RouteStepTransition; import com.sismics.docs.core.constant.RouteStepType; import com.sismics.docs.core.dao.jpa.AclDao; import com.sismics.docs.core.dao.jpa.RouteDao; import com.sismics.docs.core.dao.jpa.RouteModelDao; import com.sismics.docs.core.dao.jpa.RouteStepDao; +import com.sismics.docs.core.dao.jpa.dto.RouteStepDto; import com.sismics.docs.core.model.jpa.Route; import com.sismics.docs.core.model.jpa.RouteModel; import com.sismics.docs.core.model.jpa.RouteStep; @@ -14,6 +16,7 @@ import com.sismics.docs.core.util.RoutingUtil; import com.sismics.docs.core.util.SecurityUtil; import com.sismics.rest.exception.ClientException; import com.sismics.rest.exception.ForbiddenClientException; +import com.sismics.rest.util.ValidationUtil; import javax.json.*; import javax.ws.rs.FormParam; @@ -79,9 +82,8 @@ public class RouteResource extends BaseResource { RouteStepDao routeStepDao = new RouteStepDao(); try (JsonReader reader = Json.createReader(new StringReader(routeModel.getSteps()))) { JsonArray stepsJson = reader.readArray(); - int order = 0; - for (int i = 0; i < stepsJson.size(); i++) { - JsonObject step = stepsJson.getJsonObject(i); + for (int order = 0; order < stepsJson.size(); order++) { + JsonObject step = stepsJson.getJsonObject(order); JsonObject target = step.getJsonObject("target"); AclTargetType targetType = AclTargetType.valueOf(target.getString("type")); String targetName = target.getString("name"); @@ -89,7 +91,7 @@ public class RouteResource extends BaseResource { RouteStep routeStep = new RouteStep() .setRouteId(route.getId()) .setName(step.getString("name")) - .setOrder(order++) + .setOrder(order) .setType(RouteStepType.valueOf(step.getString("type"))) .setTargetId(SecurityUtil.getTargetIdFromName(targetName, targetType)); @@ -98,18 +100,82 @@ public class RouteResource extends BaseResource { } routeStepDao.create(routeStep); - - if (i == 0) { - // Initialize ACL on the first step - RoutingUtil.updateAcl(documentId, routeStep, null, principal.getId()); - // TODO Send an email to the targetId users - } } } + // Intialize ACLs on the first step + RouteStepDto routeStep = routeStepDao.getCurrentStep(documentId); + RoutingUtil.updateAcl(documentId, routeStep, null, principal.getId()); + // Always return OK JsonObjectBuilder response = Json.createObjectBuilder() .add("status", "ok"); return Response.ok().entity(response.build()).build(); } + + /** + * Validate the current step of a route. + * + * @api {post} /route/validate Validate the current step of a route + * @apiName PostRouteValidate + * @apiRouteModel Route + * @apiParam {String} documentId Document ID + * @apiParam {String} transition Route step transition + * @apiParam {String} comment Route step comment + * @apiSuccess {String} status Status OK + * @apiError (client) ForbiddenError Access denied + * @apiError (client) NotFound Document or route not found + * @apiPermission user + * @apiVersion 1.5.0 + * + * @return Response + */ + @POST + @Path("validate") + public Response validate(@FormParam("documentId") String documentId, + @FormParam("transition") String transitionStr, + @FormParam("comment") String comment) { + if (!authenticate()) { + throw new ForbiddenClientException(); + } + + // Get the document + AclDao aclDao = new AclDao(); + if (!aclDao.checkPermission(documentId, PermType.READ, getTargetIdList(null))) { + throw new NotFoundException(); + } + + // Get the current step + RouteStepDao routeStepDao = new RouteStepDao(); + RouteStepDto routeStep = routeStepDao.getCurrentStep(documentId); + if (routeStep == null) { + throw new NotFoundException(); + } + + // Check permission to validate this step + if (!getTargetIdList(null).contains(routeStep.getTargetId())) { + throw new ForbiddenClientException(); + } + + // Validate data + ValidationUtil.validateRequired(transitionStr, "transition"); + comment = ValidationUtil.validateLength(comment, "comment", 1, 500, true); + RouteStepTransition transition = RouteStepTransition.valueOf(transitionStr); + if (routeStep.getType() == RouteStepType.VALIDATE && transition != RouteStepTransition.VALIDATED + || routeStep.getType() == RouteStepType.APPROVE && transition != RouteStepTransition.APPROVED && transition != RouteStepTransition.REJECTED) { + throw new ClientException("ValidationError", "Invalid transition for this route step type"); + } + + // Validate the step and update ACLs + routeStepDao.endRouteStep(routeStep.getId(), transition, comment, principal.getId()); + RouteStepDto newRouteStep = routeStepDao.getCurrentStep(documentId); + RoutingUtil.updateAcl(documentId, newRouteStep, routeStep, principal.getId()); + // TODO Send an email to the new route step + + // Always return OK + // TODO Return if the document is still readable and return the new current step if any + JsonObjectBuilder response = Json.createObjectBuilder() + .add("status", "ok"); + return Response.ok().entity(response.build()).build(); + } } diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/ShareResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/ShareResource.java index cc3d28c6..f52c6968 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/ShareResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/ShareResource.java @@ -2,7 +2,6 @@ package com.sismics.docs.rest.resource; import com.sismics.docs.core.constant.AclTargetType; -import com.sismics.docs.core.constant.AclType; import com.sismics.docs.core.constant.PermType; import com.sismics.docs.core.dao.jpa.AclDao; import com.sismics.docs.core.dao.jpa.ShareDao; @@ -77,7 +76,6 @@ public class ShareResource extends BaseResource { Acl acl = new Acl(); acl.setSourceId(documentId); acl.setPerm(PermType.READ); - acl.setType(AclType.USER); acl.setTargetId(share.getId()); aclDao.create(acl, principal.getId()); @@ -121,7 +119,7 @@ public class ShareResource extends BaseResource { if (aclList.isEmpty()) { throw new ClientException("ShareNotFound", MessageFormat.format("Share not found: {0}", id)); } - + Acl acl = aclList.get(0); if (!aclDao.checkPermission(acl.getSourceId(), PermType.WRITE, getTargetIdList(null))) { throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", acl.getSourceId())); diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestRouteResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestRouteResource.java index 5464f51c..4eb583f4 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestRouteResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestRouteResource.java @@ -8,6 +8,7 @@ import javax.json.JsonArray; import javax.json.JsonObject; import javax.ws.rs.client.Entity; import javax.ws.rs.core.Form; +import javax.ws.rs.core.Response; import java.util.Date; @@ -64,6 +65,7 @@ public class TestRouteResource extends BaseJerseyTest { JsonObject routeStep = json.getJsonObject("route_step"); Assert.assertNotNull(routeStep); Assert.assertFalse(routeStep.getBoolean("transitionable")); + Assert.assertEquals("Check the document's metadata", routeStep.getString("name")); // Get document 1 as admin json = target().path("/document/" + document1Id).request() @@ -72,5 +74,56 @@ public class TestRouteResource extends BaseJerseyTest { routeStep = json.getJsonObject("route_step"); Assert.assertNotNull(routeStep); Assert.assertTrue(routeStep.getBoolean("transitionable")); + Assert.assertEquals("Check the document's metadata", routeStep.getString("name")); + + // Validate the current step with admin + target().path("/route/validate").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .post(Entity.form(new Form() + .param("documentId", document1Id) + .param("transition", "VALIDATED")), JsonObject.class); + + // Get document 1 as admin + json = target().path("/document/" + document1Id).request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .get(JsonObject.class); + routeStep = json.getJsonObject("route_step"); + Assert.assertNotNull(routeStep); + Assert.assertEquals("Add relevant files to the document", routeStep.getString("name")); + + // Validate the current step with admin + target().path("/route/validate").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .post(Entity.form(new Form() + .param("documentId", document1Id) + .param("transition", "VALIDATED") + .param("comment", "OK")), JsonObject.class); + + // Get document 1 as admin + json = target().path("/document/" + document1Id).request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .get(JsonObject.class); + routeStep = json.getJsonObject("route_step"); + Assert.assertNotNull(routeStep); + Assert.assertEquals("Approve the document", routeStep.getString("name")); + + // Validate the current step with admin + target().path("/route/validate").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .post(Entity.form(new Form() + .param("documentId", document1Id) + .param("transition", "APPROVED")), JsonObject.class); + + // Get document 1 as admin + Response response = target().path("/document/" + document1Id).request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .get(); + Assert.assertEquals(Response.Status.NOT_FOUND, Response.Status.fromStatusCode(response.getStatus())); + + // Get document 1 as admin + json = target().path("/document/" + document1Id).request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, route1Token) + .get(JsonObject.class); + Assert.assertFalse(json.containsKey("route_step")); } } \ No newline at end of file