diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java index e8dfc68e..c2e3337c 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java @@ -217,7 +217,7 @@ public class DocumentDao { if (!Strings.isNullOrEmpty(criteria.getSearch()) || !Strings.isNullOrEmpty(criteria.getFullSearch())) { LuceneDao luceneDao = new LuceneDao(); Set documentIdList = luceneDao.search(criteria.getSearch(), criteria.getFullSearch()); - if (documentIdList.size() == 0) { + if (documentIdList.isEmpty()) { // If the search doesn't find any document, the request should return nothing documentIdList.add(UUID.randomUUID().toString()); } 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 f202b58f..569de7d0 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,7 +6,9 @@ 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; /** @@ -36,4 +38,12 @@ 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 7b6bcc6c..1b974240 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 @@ -4,7 +4,9 @@ import com.sismics.docs.core.model.jpa.RouteStep; 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; /** @@ -30,4 +32,12 @@ public class RouteStepDao { return routeStep.getId(); } + + @SuppressWarnings("unchecked") + public List getRouteSteps(String routeId) { + 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(); + } } diff --git a/docs-core/src/main/java/com/sismics/docs/core/model/jpa/Acl.java b/docs-core/src/main/java/com/sismics/docs/core/model/jpa/Acl.java index cb972e6a..06d9eabc 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/model/jpa/Acl.java +++ b/docs-core/src/main/java/com/sismics/docs/core/model/jpa/Acl.java @@ -1,17 +1,11 @@ package com.sismics.docs.core.model.jpa; -import java.util.Date; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Id; -import javax.persistence.Table; - import com.google.common.base.MoreObjects; import com.sismics.docs.core.constant.PermType; +import javax.persistence.*; +import java.util.Date; + /** * ACL entity. * @@ -34,6 +28,8 @@ public class Acl implements Loggable { @Enumerated(EnumType.STRING) private PermType perm; + // TODO Add ACL type enum: USER, ROUTING + /** * ACL source ID. */ diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/EncryptionUtil.java b/docs-core/src/main/java/com/sismics/docs/core/util/EncryptionUtil.java index c47883c1..765124d6 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/util/EncryptionUtil.java +++ b/docs-core/src/main/java/com/sismics/docs/core/util/EncryptionUtil.java @@ -24,7 +24,6 @@ import java.security.Security; * @author bgamard */ public class EncryptionUtil { - /** * Salt. */ diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/EntityManagerUtil.java b/docs-core/src/main/java/com/sismics/docs/core/util/EntityManagerUtil.java deleted file mode 100644 index 142254ce..00000000 --- a/docs-core/src/main/java/com/sismics/docs/core/util/EntityManagerUtil.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.sismics.docs.core.util; - -import com.sismics.util.context.ThreadLocalContext; - -/** - * Entity manager utils. - * - * @author jtremeaux - */ -public class EntityManagerUtil { - /** - * Flush the entity manager session. - */ - public static void flush() { - ThreadLocalContext.get().getEntityManager().flush(); - } -} 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 new file mode 100644 index 00000000..45c8c23c --- /dev/null +++ b/docs-core/src/main/java/com/sismics/docs/core/util/RoutingUtil.java @@ -0,0 +1,41 @@ +package com.sismics.docs.core.util; + +import com.sismics.docs.core.dao.jpa.RouteDao; +import com.sismics.docs.core.dao.jpa.RouteStepDao; +import com.sismics.docs.core.model.jpa.Route; +import com.sismics.docs.core.model.jpa.RouteStep; + +import java.util.List; + +/** + * Routing utilities. + * + * @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; + } +} diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/SecurityUtil.java b/docs-core/src/main/java/com/sismics/docs/core/util/SecurityUtil.java new file mode 100644 index 00000000..1647bd70 --- /dev/null +++ b/docs-core/src/main/java/com/sismics/docs/core/util/SecurityUtil.java @@ -0,0 +1,40 @@ +package com.sismics.docs.core.util; + +import com.sismics.docs.core.constant.AclTargetType; +import com.sismics.docs.core.dao.jpa.GroupDao; +import com.sismics.docs.core.dao.jpa.UserDao; +import com.sismics.docs.core.model.jpa.Group; +import com.sismics.docs.core.model.jpa.User; + +/** + * Security utilities. + * + * @author bgamard + */ +public class SecurityUtil { + /** + * Get an ACL target ID from an object name and type. + * + * @param name Object name + * @param type Object type + * @return Target ID + */ + public static String getTargetIdFromName(String name, AclTargetType type) { + switch (type) { + case USER: + UserDao userDao = new UserDao(); + User user = userDao.getActiveByUsername(name); + if (user != null) { + return user.getId(); + } + case GROUP: + GroupDao groupDao = new GroupDao(); + Group group = groupDao.getActiveByName(name); + if (group != null) { + return group.getId(); + } + } + + return null; + } +} diff --git a/docs-core/src/main/java/com/sismics/docs/core/util/StreamUtil.java b/docs-core/src/main/java/com/sismics/docs/core/util/StreamUtil.java deleted file mode 100644 index 45970520..00000000 --- a/docs-core/src/main/java/com/sismics/docs/core/util/StreamUtil.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.sismics.docs.core.util; - -import java.io.IOException; -import java.io.InputStream; -import java.io.PushbackInputStream; -import java.util.zip.GZIPInputStream; - -/** - * Stream utilities. - * - * @author bgamard - */ -public class StreamUtil { - - /** - * Detects if the stream is gzipped, and returns a uncompressed stream according to this. - * - * @param is InputStream - * @return InputStream - * @throws IOException - */ - public static InputStream detectGzip(InputStream is) throws IOException { - PushbackInputStream pb = new PushbackInputStream(is, 2); - byte [] signature = new byte[2]; - pb.read(signature); - pb.unread(signature); - if(signature[0] == (byte) GZIPInputStream.GZIP_MAGIC && signature[1] == (byte) (GZIPInputStream.GZIP_MAGIC >> 8)) { - return new GZIPInputStream(pb); - } else { - return pb; - } - } -} diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java index ea0c7141..2a5bc933 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java @@ -8,7 +8,10 @@ import com.sismics.docs.core.dao.jpa.criteria.GroupCriteria; import com.sismics.docs.core.dao.jpa.criteria.UserCriteria; import com.sismics.docs.core.dao.jpa.dto.GroupDto; import com.sismics.docs.core.dao.jpa.dto.UserDto; -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.Tag; +import com.sismics.docs.core.util.SecurityUtil; import com.sismics.docs.core.util.jpa.SortCriteria; import com.sismics.rest.exception.ClientException; import com.sismics.rest.exception.ForbiddenClientException; @@ -70,29 +73,8 @@ public class AclResource extends BaseResource { AclTargetType type = AclTargetType.valueOf(ValidationUtil.validateLength(typeStr, "type", 1, 10, false)); targetName = ValidationUtil.validateLength(targetName, "target", 1, 50, false); - // Search user or group - String targetId = null; - switch (type) { - case USER: - UserDao userDao = new UserDao(); - User user = userDao.getActiveByUsername(targetName); - if (user != null) { - targetId = user.getId(); - } - break; - case GROUP: - GroupDao groupDao = new GroupDao(); - Group group = groupDao.getActiveByName(targetName); - if (group != null) { - targetId = group.getId(); - } - break; - case SHARE: - // Share must use the Share REST resource - break; - } - // Does a target has been found? + String targetId = SecurityUtil.getTargetIdFromName(targetName, type); if (targetId == null) { throw new ClientException("InvalidTarget", MessageFormat.format("This target does not exist: {0}", targetName)); } 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 6d692aa1..33d399ba 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 @@ -2,7 +2,6 @@ package com.sismics.docs.rest.resource; import com.google.common.base.Joiner; import com.google.common.base.Strings; -import com.google.common.io.ByteStreams; import com.sismics.docs.core.constant.Constants; import com.sismics.docs.core.constant.PermType; import com.sismics.docs.core.dao.jpa.*; @@ -13,11 +12,9 @@ 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.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.model.jpa.*; 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; @@ -42,7 +39,6 @@ import javax.ws.rs.*; import javax.ws.rs.core.Response; import javax.ws.rs.core.StreamingOutput; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.text.MessageFormat; import java.util.*; @@ -102,6 +98,10 @@ public class DocumentResource extends BaseResource { * @apiSuccess {String} relations.id ID * @apiSuccess {String} relations.title Title * @apiSuccess {String} relations.source True if this document is the source of the relation + * @apiSuccess {Object} route_step The current active route step + * @apiSuccess {String} route_step.name Route step name + * @apiSuccess {String="APPROVE", "VALIDATE"} route_step.type Route step type + * @apiSuccess {Boolean} route_step.transitionable True if the route step is actionable by the current user * @apiError (client) NotFound Document not found * @apiPermission none * @apiVersion 1.5.0 @@ -209,6 +209,15 @@ public class DocumentResource extends BaseResource { .add("source", relationDto.isSource())); } document.add("relations", relationList); + + // Add current route step + RouteStep routeStep = RoutingUtil.getCurrentStep(documentId); + if (routeStep != 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()))); + } return Response.ok().entity(document.build()).build(); } @@ -423,7 +432,7 @@ public class DocumentResource extends BaseResource { if (documentCriteria.getTagIdList() == null) { documentCriteria.setTagIdList(new ArrayList()); } - if (tagDtoList.size() == 0) { + if (tagDtoList.isEmpty()) { // No tag found, the request must returns nothing documentCriteria.getTagIdList().add(UUID.randomUUID().toString()); } diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/RouteModelResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/RouteModelResource.java index 9db56f2e..cfeff8a4 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/RouteModelResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/RouteModelResource.java @@ -123,7 +123,7 @@ public class RouteModelResource extends BaseResource { try (JsonReader reader = Json.createReader(new StringReader(steps))) { JsonArray stepsJson = reader.readArray(); - if (stepsJson.size() == 0) { + if (stepsJson.isEmpty()) { throw new ClientException("ValidationError", "At least one step is required"); } for (int i = 0; i < stepsJson.size(); i++) { 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 337dc8d8..56a78a5d 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 @@ -3,8 +3,14 @@ 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.RouteStepType; -import com.sismics.docs.core.dao.jpa.*; -import com.sismics.docs.core.model.jpa.*; +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.model.jpa.Route; +import com.sismics.docs.core.model.jpa.RouteModel; +import com.sismics.docs.core.model.jpa.RouteStep; +import com.sismics.docs.core.util.SecurityUtil; import com.sismics.rest.exception.ClientException; import com.sismics.rest.exception.ForbiddenClientException; @@ -69,8 +75,6 @@ public class RouteResource extends BaseResource { routeDao.create(route, principal.getId()); // Create the steps - UserDao userDao = new UserDao(); - GroupDao groupDao = new GroupDao(); RouteStepDao routeStepDao = new RouteStepDao(); try (JsonReader reader = Json.createReader(new StringReader(routeModel.getSteps()))) { JsonArray stepsJson = reader.readArray(); @@ -85,22 +89,8 @@ public class RouteResource extends BaseResource { .setRouteId(route.getId()) .setName(step.getString("name")) .setOrder(order++) - .setType(RouteStepType.valueOf(step.getString("type"))); - - switch (targetType) { - case USER: - User user = userDao.getActiveByUsername(targetName); - if (user != null) { - routeStep.setTargetId(user.getId()); - } - break; - case GROUP: - Group group = groupDao.getActiveByName(targetName); - if (group != null) { - routeStep.setTargetId(group.getId()); - } - break; - } + .setType(RouteStepType.valueOf(step.getString("type"))) + .setTargetId(SecurityUtil.getTargetIdFromName(targetName, targetType)); if (routeStep.getTargetId() == null) { throw new ClientException("InvalidRouteModel", "A step has an invalid target"); 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 28d9eb7c..c50b9094 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 @@ -116,7 +116,7 @@ public class ShareResource extends BaseResource { // Check that the user can share the linked document AclDao aclDao = new AclDao(); List aclList = aclDao.getByTargetId(id); - if (aclList.size() == 0) { + if (aclList.isEmpty()) { throw new ClientException("ShareNotFound", MessageFormat.format("Share not found: {0}", id)); } diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java index 452c2268..5f310fda 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/TagResource.java @@ -116,7 +116,7 @@ public class TagResource extends BaseResource { TagDao tagDao = new TagDao(); List tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)).setId(id), null); - if (tagDtoList.size() == 0) { + if (tagDtoList.isEmpty()) { throw new NotFoundException(); } diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java index 99bbae7c..3ece751a 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java @@ -131,7 +131,7 @@ public class TestDocumentResource extends BaseJerseyTest { .cookie(TokenBasedSecurityFilter.COOKIE_NAME, document3Token) .get(JsonObject.class); documents = json.getJsonArray("documents"); - Assert.assertTrue(documents.size() == 0); + Assert.assertTrue(documents.isEmpty()); // Create a document with document3 long create3Date = new Date().getTime(); 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 e628c62a..bbe95519 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 @@ -22,6 +22,10 @@ public class TestRouteResource extends BaseJerseyTest { */ @Test public void testRouteResource() { + // Login route1 + clientUtil.createUser("route1"); + String route1Token = clientUtil.login("route1"); + // Login admin String adminToken = clientUtil.login("admin", "admin", false); @@ -30,7 +34,7 @@ public class TestRouteResource extends BaseJerseyTest { .queryParam("sort_column", "2") .queryParam("asc", "false") .request() - .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, route1Token) .get(JsonObject.class); JsonArray routeModels = json.getJsonArray("routemodels"); Assert.assertEquals(1, routeModels.size()); @@ -38,7 +42,7 @@ public class TestRouteResource extends BaseJerseyTest { // Create a document long create1Date = new Date().getTime(); json = target().path("/document").request() - .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, route1Token) .put(Entity.form(new Form() .param("title", "My super title document 1") .param("description", "My super description for document 1") @@ -46,11 +50,37 @@ public class TestRouteResource extends BaseJerseyTest { .param("create_date", Long.toString(create1Date))), JsonObject.class); String document1Id = json.getString("id"); - // Start the default route on document1 + // Start the default route on document 1 target().path("/route/start").request() - .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, route1Token) .post(Entity.form(new Form() .param("documentId", document1Id) .param("routeModelId", routeModels.getJsonObject(0).getString("id"))), JsonObject.class); + + // Add an ACL READ for admin with route1 + // TODO Remove me when ACLs are automatically added on route step targets + target().path("/acl").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, route1Token) + .put(Entity.form(new Form() + .param("source", document1Id) + .param("perm", "READ") + .param("target", "admin") + .param("type", "USER")), JsonObject.class); + + // Get document 1 as route1 + json = target().path("/document/" + document1Id).request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, route1Token) + .get(JsonObject.class); + JsonObject routeStep = json.getJsonObject("route_step"); + Assert.assertNotNull(routeStep); + Assert.assertFalse(routeStep.getBoolean("transitionable")); + + // 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.assertTrue(routeStep.getBoolean("transitionable")); } } \ No newline at end of file