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 new file mode 100644 index 00000000..f202b58f --- /dev/null +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/RouteDao.java @@ -0,0 +1,39 @@ +package com.sismics.docs.core.dao.jpa; + +import com.sismics.docs.core.constant.AuditLogType; +import com.sismics.docs.core.model.jpa.Route; +import com.sismics.docs.core.util.AuditLogUtil; +import com.sismics.util.context.ThreadLocalContext; + +import javax.persistence.EntityManager; +import java.util.Date; +import java.util.UUID; + +/** + * Route DAO. + * + * @author bgamard + */ +public class RouteDao { + /** + * Creates a new route. + * + * @param route Route + * @param userId User ID + * @return New ID + */ + public String create(Route route, String userId) { + // Create the UUID + route.setId(UUID.randomUUID().toString()); + + // Create the route + EntityManager em = ThreadLocalContext.get().getEntityManager(); + route.setCreateDate(new Date()); + em.persist(route); + + // Create audit log + AuditLogUtil.create(route, AuditLogType.CREATE, userId); + + return route.getId(); + } +} 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 new file mode 100644 index 00000000..7b6bcc6c --- /dev/null +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/RouteStepDao.java @@ -0,0 +1,33 @@ +package com.sismics.docs.core.dao.jpa; + +import com.sismics.docs.core.model.jpa.RouteStep; +import com.sismics.util.context.ThreadLocalContext; + +import javax.persistence.EntityManager; +import java.util.Date; +import java.util.UUID; + +/** + * Route step DAO. + * + * @author bgamard + */ +public class RouteStepDao { + /** + * Creates a new route step. + * + * @param routeStep Route step + * @return New ID + */ + public String create(RouteStep routeStep) { + // Create the UUID + routeStep.setId(UUID.randomUUID().toString()); + + // Create the route step + EntityManager em = ThreadLocalContext.get().getEntityManager(); + routeStep.setCreateDate(new Date()); + em.persist(routeStep); + + return routeStep.getId(); + } +} diff --git a/docs-core/src/main/java/com/sismics/docs/core/model/jpa/Route.java b/docs-core/src/main/java/com/sismics/docs/core/model/jpa/Route.java index b71641bb..e597a077 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/model/jpa/Route.java +++ b/docs-core/src/main/java/com/sismics/docs/core/model/jpa/Route.java @@ -15,7 +15,7 @@ import java.util.Date; */ @Entity @Table(name = "T_ROUTE") -public class Route { +public class Route implements Loggable { /** * Route ID. */ @@ -29,6 +29,12 @@ public class Route { @Column(name = "RTE_IDDOCUMENT_C", nullable = false, length = 36) private String documentId; + /** + * Name. + */ + @Column(name = "RTE_NAME_C", nullable = false, length = 50) + private String name; + /** * Creation date. */ @@ -41,10 +47,61 @@ public class Route { @Column(name = "RTE_DELETEDATE_D") private Date deleteDate; + public String getId() { + return id; + } + + public Route setId(String id) { + this.id = id; + return this; + } + + public String getDocumentId() { + return documentId; + } + + public Route setDocumentId(String documentId) { + this.documentId = documentId; + return this; + } + + public String getName() { + return name; + } + + public Route setName(String name) { + this.name = name; + return this; + } + + public Date getCreateDate() { + return createDate; + } + + public Route setCreateDate(Date createDate) { + this.createDate = createDate; + return this; + } + + public Date getDeleteDate() { + return deleteDate; + } + + public Route setDeleteDate(Date deleteDate) { + this.deleteDate = deleteDate; + return this; + } + + @Override + public String toMessage() { + return documentId; + } + @Override public String toString() { return MoreObjects.toStringHelper(this) .add("id", id) + .add("name", name) .add("documentId", documentId) .add("createDate", createDate) .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 83df4f94..c1a6b477 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 @@ -84,6 +84,105 @@ public class RouteStep { @Column(name = "RTP_DELETEDATE_D") private Date deleteDate; + public String getId() { + return id; + } + + public RouteStep setId(String id) { + this.id = id; + return this; + } + + public String getRouteId() { + return routeId; + } + + public RouteStep setRouteId(String routeId) { + this.routeId = routeId; + return this; + } + + public String getName() { + return name; + } + + public RouteStep setName(String name) { + this.name = name; + return this; + } + + public RouteStepType getType() { + return type; + } + + public RouteStep setType(RouteStepType type) { + this.type = type; + return this; + } + + public RouteStepTransition getTransition() { + return transition; + } + + public RouteStep setTransition(RouteStepTransition transition) { + this.transition = transition; + return this; + } + + public String getComment() { + return comment; + } + + public RouteStep setComment(String comment) { + this.comment = comment; + return this; + } + + public String getTargetId() { + return targetId; + } + + public RouteStep setTargetId(String targetId) { + this.targetId = targetId; + return this; + } + + public Integer getOrder() { + return order; + } + + public RouteStep setOrder(Integer order) { + this.order = order; + return this; + } + + public Date getCreateDate() { + return createDate; + } + + public RouteStep setCreateDate(Date createDate) { + this.createDate = createDate; + return this; + } + + public Date getEndDate() { + return endDate; + } + + public RouteStep setEndDate(Date endDate) { + this.endDate = endDate; + return this; + } + + public Date getDeleteDate() { + return deleteDate; + } + + public RouteStep setDeleteDate(Date deleteDate) { + this.deleteDate = deleteDate; + return this; + } + @Override public String toString() { return MoreObjects.toStringHelper(this) 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 cba91ec2..11ea1fd4 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,6 +1,6 @@ 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_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, RTE_CREATEDATE_D datetime not null, RTP_ENDDATE_D datetime, RTP_DELETEDATE_D datetime, primary key (RTP_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) );; 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; 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 cd5c4f4f..9db56f2e 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 @@ -201,7 +201,8 @@ public class RouteModelResource extends BaseResource { // Validate input name = ValidationUtil.validateLength(name, "name", 1, 50, false); - // TODO Validate steps data + steps = ValidationUtil.validateLength(steps, "steps", 1, 5000, false); + validateRouteModelSteps(steps); // Get the route model RouteModelDao routeModelDao = new RouteModelDao(); 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 new file mode 100644 index 00000000..337dc8d8 --- /dev/null +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/RouteResource.java @@ -0,0 +1,118 @@ +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.rest.exception.ClientException; +import com.sismics.rest.exception.ForbiddenClientException; + +import javax.json.*; +import javax.ws.rs.FormParam; +import javax.ws.rs.NotFoundException; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.core.Response; +import java.io.StringReader; + +/** + * Route REST resources. + * + * @author bgamard + */ +@Path("/route") +public class RouteResource extends BaseResource { + /** + * Start a route on a document. + * + * @api {post} /route/start Start a route on a document + * @apiName PostRouteStart + * @apiRouteModel Route + * @apiParam {String} routeModelId Route model ID + * @apiParam {String} documentId Document ID + * @apiSuccess {String} status Status OK + * @apiError (client) InvalidRouteModel Invalid route model + * @apiError (client) ForbiddenError Access denied + * @apiError (client) NotFound Route model or document not found + * @apiPermission user + * @apiVersion 1.5.0 + * + * @return Response + */ + @POST + @Path("start") + public Response start(@FormParam("routeModelId") String routeModelId, + @FormParam("documentId") String documentId) { + if (!authenticate()) { + throw new ForbiddenClientException(); + } + + // Get the document + AclDao aclDao = new AclDao(); + if (!aclDao.checkPermission(documentId, PermType.WRITE, getTargetIdList(null))) { + throw new NotFoundException(); + } + + // Get the route model + RouteModelDao routeModelDao = new RouteModelDao(); + RouteModel routeModel = routeModelDao.getActiveById(routeModelId); + if (routeModel == null) { + throw new NotFoundException(); + } + + // Create the route + Route route = new Route() + .setDocumentId(documentId) + .setName(routeModel.getName()); + RouteDao routeDao = new RouteDao(); + 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(); + int order = 0; + for (int i = 0; i < stepsJson.size(); i++) { + JsonObject step = stepsJson.getJsonObject(i); + JsonObject target = step.getJsonObject("target"); + AclTargetType targetType = AclTargetType.valueOf(target.getString("type")); + String targetName = target.getString("name"); + + RouteStep routeStep = new RouteStep() + .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; + } + + if (routeStep.getTargetId() == null) { + throw new ClientException("InvalidRouteModel", "A step has an invalid target"); + } + + routeStepDao.create(routeStep); + } + } + + // Always return OK + JsonObjectBuilder response = Json.createObjectBuilder() + .add("status", "ok"); + return Response.ok().entity(response.build()).build(); + } +} diff --git a/docs-web/src/main/webapp/src/partial/docs/directive.auditlog.html b/docs-web/src/main/webapp/src/partial/docs/directive.auditlog.html index 691b9d30..a4f28d2b 100644 --- a/docs-web/src/main/webapp/src/partial/docs/directive.auditlog.html +++ b/docs-web/src/main/webapp/src/partial/docs/directive.auditlog.html @@ -45,6 +45,9 @@ {{ log.message }} + + {{ 'open' | translate }} + 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 new file mode 100644 index 00000000..e628c62a --- /dev/null +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestRouteResource.java @@ -0,0 +1,56 @@ +package com.sismics.docs.rest; + +import com.sismics.util.filter.TokenBasedSecurityFilter; +import org.junit.Assert; +import org.junit.Test; + +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.Form; +import java.util.Date; + + +/** + * Test the route resource. + * + * @author bgamard + */ +public class TestRouteResource extends BaseJerseyTest { + /** + * Test the route resource. + */ + @Test + public void testRouteResource() { + // Login admin + String adminToken = clientUtil.login("admin", "admin", false); + + // Get all route models + JsonObject json = target().path("/routemodel") + .queryParam("sort_column", "2") + .queryParam("asc", "false") + .request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .get(JsonObject.class); + JsonArray routeModels = json.getJsonArray("routemodels"); + Assert.assertEquals(1, routeModels.size()); + + // Create a document + long create1Date = new Date().getTime(); + json = target().path("/document").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .put(Entity.form(new Form() + .param("title", "My super title document 1") + .param("description", "My super description for document 1") + .param("language", "eng") + .param("create_date", Long.toString(create1Date))), JsonObject.class); + String document1Id = json.getString("id"); + + // Start the default route on document1 + target().path("/route/start").request() + .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken) + .post(Entity.form(new Form() + .param("documentId", document1Id) + .param("routeModelId", routeModels.getJsonObject(0).getString("id"))), JsonObject.class); + } +} \ No newline at end of file