#159: return the active step in GET /document/id

This commit is contained in:
Benjamin Gamard 2018-01-31 22:07:38 +01:00
parent 5e713f0c2a
commit 503cfff82e
17 changed files with 176 additions and 119 deletions

View File

@ -217,7 +217,7 @@ public class DocumentDao {
if (!Strings.isNullOrEmpty(criteria.getSearch()) || !Strings.isNullOrEmpty(criteria.getFullSearch())) { if (!Strings.isNullOrEmpty(criteria.getSearch()) || !Strings.isNullOrEmpty(criteria.getFullSearch())) {
LuceneDao luceneDao = new LuceneDao(); LuceneDao luceneDao = new LuceneDao();
Set<String> documentIdList = luceneDao.search(criteria.getSearch(), criteria.getFullSearch()); Set<String> 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 // If the search doesn't find any document, the request should return nothing
documentIdList.add(UUID.randomUUID().toString()); documentIdList.add(UUID.randomUUID().toString());
} }

View File

@ -6,7 +6,9 @@ import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.util.context.ThreadLocalContext; import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.UUID; import java.util.UUID;
/** /**
@ -36,4 +38,12 @@ public class RouteDao {
return route.getId(); return route.getId();
} }
@SuppressWarnings("unchecked")
public List<Route> 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();
}
} }

View File

@ -4,7 +4,9 @@ import com.sismics.docs.core.model.jpa.RouteStep;
import com.sismics.util.context.ThreadLocalContext; import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.UUID; import java.util.UUID;
/** /**
@ -30,4 +32,12 @@ public class RouteStepDao {
return routeStep.getId(); return routeStep.getId();
} }
@SuppressWarnings("unchecked")
public List<RouteStep> 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();
}
} }

View File

@ -1,17 +1,11 @@
package com.sismics.docs.core.model.jpa; 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.google.common.base.MoreObjects;
import com.sismics.docs.core.constant.PermType; import com.sismics.docs.core.constant.PermType;
import javax.persistence.*;
import java.util.Date;
/** /**
* ACL entity. * ACL entity.
* *
@ -34,6 +28,8 @@ public class Acl implements Loggable {
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
private PermType perm; private PermType perm;
// TODO Add ACL type enum: USER, ROUTING
/** /**
* ACL source ID. * ACL source ID.
*/ */

View File

@ -24,7 +24,6 @@ import java.security.Security;
* @author bgamard * @author bgamard
*/ */
public class EncryptionUtil { public class EncryptionUtil {
/** /**
* Salt. * Salt.
*/ */

View File

@ -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();
}
}

View File

@ -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<Route> routeList = routeDao.getActiveRoutes(documentId);
if (routeList.isEmpty()) {
return null;
}
Route route = routeList.get(0);
RouteStepDao routeStepDao = new RouteStepDao();
List<RouteStep> routeStepList = routeStepDao.getRouteSteps(route.getId());
for (RouteStep routeStep : routeStepList) {
if (routeStep.getEndDate() == null) {
return routeStep;
}
}
return null;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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.criteria.UserCriteria;
import com.sismics.docs.core.dao.jpa.dto.GroupDto; import com.sismics.docs.core.dao.jpa.dto.GroupDto;
import com.sismics.docs.core.dao.jpa.dto.UserDto; 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.docs.core.util.jpa.SortCriteria;
import com.sismics.rest.exception.ClientException; import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException; 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)); AclTargetType type = AclTargetType.valueOf(ValidationUtil.validateLength(typeStr, "type", 1, 10, false));
targetName = ValidationUtil.validateLength(targetName, "target", 1, 50, 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? // Does a target has been found?
String targetId = SecurityUtil.getTargetIdFromName(targetName, type);
if (targetId == null) { if (targetId == null) {
throw new ClientException("InvalidTarget", MessageFormat.format("This target does not exist: {0}", targetName)); throw new ClientException("InvalidTarget", MessageFormat.format("This target does not exist: {0}", targetName));
} }

View File

@ -2,7 +2,6 @@ package com.sismics.docs.rest.resource;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.base.Strings; 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.Constants;
import com.sismics.docs.core.constant.PermType; import com.sismics.docs.core.constant.PermType;
import com.sismics.docs.core.dao.jpa.*; 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.DocumentDeletedAsyncEvent;
import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent; import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent;
import com.sismics.docs.core.event.FileDeletedAsyncEvent; import com.sismics.docs.core.event.FileDeletedAsyncEvent;
import com.sismics.docs.core.model.jpa.Acl; import com.sismics.docs.core.model.jpa.*;
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.PdfUtil;
import com.sismics.docs.core.util.RoutingUtil;
import com.sismics.docs.core.util.jpa.PaginatedList; import com.sismics.docs.core.util.jpa.PaginatedList;
import com.sismics.docs.core.util.jpa.PaginatedLists; import com.sismics.docs.core.util.jpa.PaginatedLists;
import com.sismics.docs.core.util.jpa.SortCriteria; 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.Response;
import javax.ws.rs.core.StreamingOutput; import javax.ws.rs.core.StreamingOutput;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.*; import java.util.*;
@ -102,6 +98,10 @@ public class DocumentResource extends BaseResource {
* @apiSuccess {String} relations.id ID * @apiSuccess {String} relations.id ID
* @apiSuccess {String} relations.title Title * @apiSuccess {String} relations.title Title
* @apiSuccess {String} relations.source True if this document is the source of the relation * @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 * @apiError (client) NotFound Document not found
* @apiPermission none * @apiPermission none
* @apiVersion 1.5.0 * @apiVersion 1.5.0
@ -210,6 +210,15 @@ public class DocumentResource extends BaseResource {
} }
document.add("relations", relationList); 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(); return Response.ok().entity(document.build()).build();
} }
@ -423,7 +432,7 @@ public class DocumentResource extends BaseResource {
if (documentCriteria.getTagIdList() == null) { if (documentCriteria.getTagIdList() == null) {
documentCriteria.setTagIdList(new ArrayList<String>()); documentCriteria.setTagIdList(new ArrayList<String>());
} }
if (tagDtoList.size() == 0) { if (tagDtoList.isEmpty()) {
// No tag found, the request must returns nothing // No tag found, the request must returns nothing
documentCriteria.getTagIdList().add(UUID.randomUUID().toString()); documentCriteria.getTagIdList().add(UUID.randomUUID().toString());
} }

View File

@ -123,7 +123,7 @@ public class RouteModelResource extends BaseResource {
try (JsonReader reader = Json.createReader(new StringReader(steps))) { try (JsonReader reader = Json.createReader(new StringReader(steps))) {
JsonArray stepsJson = reader.readArray(); JsonArray stepsJson = reader.readArray();
if (stepsJson.size() == 0) { if (stepsJson.isEmpty()) {
throw new ClientException("ValidationError", "At least one step is required"); throw new ClientException("ValidationError", "At least one step is required");
} }
for (int i = 0; i < stepsJson.size(); i++) { for (int i = 0; i < stepsJson.size(); i++) {

View File

@ -3,8 +3,14 @@ package com.sismics.docs.rest.resource;
import com.sismics.docs.core.constant.AclTargetType; import com.sismics.docs.core.constant.AclTargetType;
import com.sismics.docs.core.constant.PermType; import com.sismics.docs.core.constant.PermType;
import com.sismics.docs.core.constant.RouteStepType; import com.sismics.docs.core.constant.RouteStepType;
import com.sismics.docs.core.dao.jpa.*; import com.sismics.docs.core.dao.jpa.AclDao;
import com.sismics.docs.core.model.jpa.*; 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.ClientException;
import com.sismics.rest.exception.ForbiddenClientException; import com.sismics.rest.exception.ForbiddenClientException;
@ -69,8 +75,6 @@ public class RouteResource extends BaseResource {
routeDao.create(route, principal.getId()); routeDao.create(route, principal.getId());
// Create the steps // Create the steps
UserDao userDao = new UserDao();
GroupDao groupDao = new GroupDao();
RouteStepDao routeStepDao = new RouteStepDao(); RouteStepDao routeStepDao = new RouteStepDao();
try (JsonReader reader = Json.createReader(new StringReader(routeModel.getSteps()))) { try (JsonReader reader = Json.createReader(new StringReader(routeModel.getSteps()))) {
JsonArray stepsJson = reader.readArray(); JsonArray stepsJson = reader.readArray();
@ -85,22 +89,8 @@ public class RouteResource extends BaseResource {
.setRouteId(route.getId()) .setRouteId(route.getId())
.setName(step.getString("name")) .setName(step.getString("name"))
.setOrder(order++) .setOrder(order++)
.setType(RouteStepType.valueOf(step.getString("type"))); .setType(RouteStepType.valueOf(step.getString("type")))
.setTargetId(SecurityUtil.getTargetIdFromName(targetName, targetType));
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;
}
if (routeStep.getTargetId() == null) { if (routeStep.getTargetId() == null) {
throw new ClientException("InvalidRouteModel", "A step has an invalid target"); throw new ClientException("InvalidRouteModel", "A step has an invalid target");

View File

@ -116,7 +116,7 @@ public class ShareResource extends BaseResource {
// Check that the user can share the linked document // Check that the user can share the linked document
AclDao aclDao = new AclDao(); AclDao aclDao = new AclDao();
List<Acl> aclList = aclDao.getByTargetId(id); List<Acl> aclList = aclDao.getByTargetId(id);
if (aclList.size() == 0) { if (aclList.isEmpty()) {
throw new ClientException("ShareNotFound", MessageFormat.format("Share not found: {0}", id)); throw new ClientException("ShareNotFound", MessageFormat.format("Share not found: {0}", id));
} }

View File

@ -116,7 +116,7 @@ public class TagResource extends BaseResource {
TagDao tagDao = new TagDao(); TagDao tagDao = new TagDao();
List<TagDto> tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)).setId(id), null); List<TagDto> tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)).setId(id), null);
if (tagDtoList.size() == 0) { if (tagDtoList.isEmpty()) {
throw new NotFoundException(); throw new NotFoundException();
} }

View File

@ -131,7 +131,7 @@ public class TestDocumentResource extends BaseJerseyTest {
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, document3Token) .cookie(TokenBasedSecurityFilter.COOKIE_NAME, document3Token)
.get(JsonObject.class); .get(JsonObject.class);
documents = json.getJsonArray("documents"); documents = json.getJsonArray("documents");
Assert.assertTrue(documents.size() == 0); Assert.assertTrue(documents.isEmpty());
// Create a document with document3 // Create a document with document3
long create3Date = new Date().getTime(); long create3Date = new Date().getTime();

View File

@ -22,6 +22,10 @@ public class TestRouteResource extends BaseJerseyTest {
*/ */
@Test @Test
public void testRouteResource() { public void testRouteResource() {
// Login route1
clientUtil.createUser("route1");
String route1Token = clientUtil.login("route1");
// Login admin // Login admin
String adminToken = clientUtil.login("admin", "admin", false); String adminToken = clientUtil.login("admin", "admin", false);
@ -30,7 +34,7 @@ public class TestRouteResource extends BaseJerseyTest {
.queryParam("sort_column", "2") .queryParam("sort_column", "2")
.queryParam("asc", "false") .queryParam("asc", "false")
.request() .request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) .cookie(TokenBasedSecurityFilter.COOKIE_NAME, route1Token)
.get(JsonObject.class); .get(JsonObject.class);
JsonArray routeModels = json.getJsonArray("routemodels"); JsonArray routeModels = json.getJsonArray("routemodels");
Assert.assertEquals(1, routeModels.size()); Assert.assertEquals(1, routeModels.size());
@ -38,7 +42,7 @@ public class TestRouteResource extends BaseJerseyTest {
// Create a document // Create a document
long create1Date = new Date().getTime(); long create1Date = new Date().getTime();
json = target().path("/document").request() json = target().path("/document").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) .cookie(TokenBasedSecurityFilter.COOKIE_NAME, route1Token)
.put(Entity.form(new Form() .put(Entity.form(new Form()
.param("title", "My super title document 1") .param("title", "My super title document 1")
.param("description", "My super description for 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); .param("create_date", Long.toString(create1Date))), JsonObject.class);
String document1Id = json.getString("id"); String document1Id = json.getString("id");
// Start the default route on document1 // Start the default route on document 1
target().path("/route/start").request() target().path("/route/start").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) .cookie(TokenBasedSecurityFilter.COOKIE_NAME, route1Token)
.post(Entity.form(new Form() .post(Entity.form(new Form()
.param("documentId", document1Id) .param("documentId", document1Id)
.param("routeModelId", routeModels.getJsonObject(0).getString("id"))), JsonObject.class); .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"));
} }
} }