Closes #68: Add contributors list on documents

This commit is contained in:
jendib 2016-02-21 23:43:35 +01:00
parent 2824878065
commit 943465a390
10 changed files with 279 additions and 16 deletions

View File

@ -0,0 +1,80 @@
package com.sismics.docs.core.dao.jpa;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import com.sismics.docs.core.dao.jpa.dto.ContributorDto;
import com.sismics.docs.core.model.jpa.Contributor;
import com.sismics.util.context.ThreadLocalContext;
/**
* Contributor DAO.
*
* @author bgamard
*/
public class ContributorDao {
/**
* Creates a new contributor.
*
* @param contributor Contributor
* @param userId User ID
* @return New ID
* @throws Exception
*/
public String create(Contributor contributor) {
// Create the UUID
contributor.setId(UUID.randomUUID().toString());
// Create the contributor
EntityManager em = ThreadLocalContext.get().getEntityManager();
em.persist(contributor);
return contributor.getId();
}
/**
* Returns the list of all contributors by document.
*
* @param documentId Document ID
* @return List of contributors
*/
@SuppressWarnings("unchecked")
public List<Contributor> findByDocumentId(String documentId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select c from Contributor c where c.documentId = :documentId");
q.setParameter("documentId", documentId);
return q.getResultList();
}
/**
* Returns the list of all contributors by document.
*
* @param documentId Document ID
* @return List of contributors
*/
@SuppressWarnings("unchecked")
public List<ContributorDto> getByDocumentId(String documentId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select u.USE_USERNAME_C, u.USE_EMAIL_C from T_CONTRIBUTOR c ");
sb.append(" join T_USER u on u.USE_ID_C = c.CTR_IDUSER_C ");
sb.append(" where c.CTR_IDDOC_C = :documentId ");
Query q = em.createNativeQuery(sb.toString());
q.setParameter("documentId", documentId);
List<Object[]> l = q.getResultList();
// Assemble results
List<ContributorDto> contributorDtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
ContributorDto contributorDto = new ContributorDto();
contributorDto.setUsername((String) o[i++]);
contributorDto.setEmail((String) o[i++]);
contributorDtoList.add(contributorDto);
}
return contributorDtoList;
}
}

View File

@ -136,9 +136,10 @@ public class DocumentDao {
*/ */
public Document getDocument(String id, PermType perm, String userId) { public Document getDocument(String id, PermType perm, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager(); EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createNativeQuery("select d.* from T_DOCUMENT d " StringBuilder sb = new StringBuilder("select d.* from T_DOCUMENT d ");
+ " join T_ACL a on a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_TARGETID_C = :userId and a.ACL_PERM_C = :perm and a.ACL_DELETEDATE_D is null " sb.append(" join T_ACL a on a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_TARGETID_C = :userId and a.ACL_PERM_C = :perm and a.ACL_DELETEDATE_D is null ");
+ " where d.DOC_ID_C = :id and d.DOC_DELETEDATE_D is null", Document.class); sb.append(" where d.DOC_ID_C = :id and d.DOC_DELETEDATE_D is null");
Query q = em.createNativeQuery(sb.toString(), Document.class);
q.setParameter("id", id); q.setParameter("id", id);
q.setParameter("perm", perm.name()); q.setParameter("perm", perm.name());
q.setParameter("userId", userId); q.setParameter("userId", userId);
@ -276,7 +277,7 @@ public class DocumentDao {
List<Object[]> l = PaginatedLists.executePaginatedQuery(paginatedList, queryParam, sortCriteria); List<Object[]> l = PaginatedLists.executePaginatedQuery(paginatedList, queryParam, sortCriteria);
// Assemble results // Assemble results
List<DocumentDto> documentDtoList = new ArrayList<DocumentDto>(); List<DocumentDto> documentDtoList = new ArrayList<>();
for (Object[] o : l) { for (Object[] o : l) {
int i = 0; int i = 0;
DocumentDto documentDto = new DocumentDto(); DocumentDto documentDto = new DocumentDto();

View File

@ -0,0 +1,34 @@
package com.sismics.docs.core.dao.jpa.dto;
/**
* Contributor DTO.
*
* @author bgamard
*/
public class ContributorDto {
/**
* Username.
*/
private String username;
/**
* Email.
*/
private String email;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}

View File

@ -4,8 +4,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import com.sismics.docs.core.dao.jpa.ContributorDao;
import com.sismics.docs.core.dao.lucene.LuceneDao; import com.sismics.docs.core.dao.lucene.LuceneDao;
import com.sismics.docs.core.event.DocumentCreatedAsyncEvent; import com.sismics.docs.core.event.DocumentCreatedAsyncEvent;
import com.sismics.docs.core.model.jpa.Contributor;
import com.sismics.docs.core.util.TransactionUtil;
/** /**
* Listener on document created. * Listener on document created.
@ -21,17 +24,29 @@ public class DocumentCreatedAsyncListener {
/** /**
* Document created. * Document created.
* *
* @param documentCreatedAsyncEvent Document created event * @param event Document created event
* @throws Exception * @throws Exception
*/ */
@Subscribe @Subscribe
public void on(final DocumentCreatedAsyncEvent documentCreatedAsyncEvent) throws Exception { public void on(final DocumentCreatedAsyncEvent event) throws Exception {
if (log.isInfoEnabled()) { if (log.isInfoEnabled()) {
log.info("Document created event: " + documentCreatedAsyncEvent.toString()); log.info("Document created event: " + event.toString());
} }
TransactionUtil.handle(new Runnable() {
@Override
public void run() {
// Add the first contributor (the creator of the document)
ContributorDao contributorDao = new ContributorDao();
Contributor contributor = new Contributor();
contributor.setDocumentId(event.getDocument().getId());
contributor.setUserId(event.getUserId());
contributorDao.create(contributor);
}
});
// Update Lucene index // Update Lucene index
LuceneDao luceneDao = new LuceneDao(); LuceneDao luceneDao = new LuceneDao();
luceneDao.createDocument(documentCreatedAsyncEvent.getDocument()); luceneDao.createDocument(event.getDocument());
} }
} }

View File

@ -1,11 +1,16 @@
package com.sismics.docs.core.listener.async; package com.sismics.docs.core.listener.async;
import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import com.sismics.docs.core.dao.jpa.ContributorDao;
import com.sismics.docs.core.dao.lucene.LuceneDao; import com.sismics.docs.core.dao.lucene.LuceneDao;
import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent; import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent;
import com.sismics.docs.core.model.jpa.Contributor;
import com.sismics.docs.core.util.TransactionUtil;
/** /**
* Listener on document updated. * Listener on document updated.
@ -21,17 +26,40 @@ public class DocumentUpdatedAsyncListener {
/** /**
* Document updated. * Document updated.
* *
* @param documentUpdatedAsyncEvent Document updated event * @param event Document updated event
* @throws Exception * @throws Exception
*/ */
@Subscribe @Subscribe
public void on(final DocumentUpdatedAsyncEvent documentUpdatedAsyncEvent) throws Exception { public void on(final DocumentUpdatedAsyncEvent event) throws Exception {
if (log.isInfoEnabled()) { if (log.isInfoEnabled()) {
log.info("Document updated event: " + documentUpdatedAsyncEvent.toString()); log.info("Document updated event: " + event.toString());
} }
// Update contributors list
TransactionUtil.handle(new Runnable() {
@Override
public void run() {
ContributorDao contributorDao = new ContributorDao();
List<Contributor> contributorList = contributorDao.findByDocumentId(event.getDocument().getId());
// Check if the user firing this event is not already a contributor
for (Contributor contributor : contributorList) {
if (contributor.getUserId().equals(event.getUserId())) {
// The current user is already a contributor on this document, don't do anything
return;
}
}
// Add a new contributor
Contributor contributor = new Contributor();
contributor.setDocumentId(event.getDocument().getId());
contributor.setUserId(event.getUserId());
contributorDao.create(contributor);
}
});
// Update Lucene index // Update Lucene index
LuceneDao luceneDao = new LuceneDao(); LuceneDao luceneDao = new LuceneDao();
luceneDao.updateDocument(documentUpdatedAsyncEvent.getDocument()); luceneDao.updateDocument(event.getDocument());
} }
} }

View File

@ -0,0 +1,69 @@
package com.sismics.docs.core.model.jpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import com.google.common.base.MoreObjects;
/**
* Contributor entity.
*
* @author bgamard
*/
@Entity
@Table(name = "T_Contributor")
public class Contributor {
/**
* Contributor ID.
*/
@Id
@Column(name = "CTR_ID_C", length = 36)
private String id;
/**
* Document ID.
*/
@Column(name = "CTR_IDDOC_C", length = 36, nullable = false)
private String documentId;
/**
* User ID.
*/
@Column(name = "CTR_IDUSER_C", length = 36, nullable = false)
private String userId;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getDocumentId() {
return documentId;
}
public void setDocumentId(String documentId) {
this.documentId = documentId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("id", id)
.add("userId", userId)
.add("documentId", documentId)
.toString();
}
}

View File

@ -9,6 +9,7 @@ alter table T_DOCUMENT add column DOC_RIGHTS_C varchar(500);
alter table T_AUDIT_LOG add column LOG_IDUSER_C varchar(36) not null default 'admin'; alter table T_AUDIT_LOG add column LOG_IDUSER_C varchar(36) not null default 'admin';
create memory table T_VOCABULARY ( VOC_ID_C varchar(36) not null, VOC_NAME_C varchar(50) not null, VOC_VALUE_C varchar(500) not null, VOC_ORDER_N int not null, primary key (VOC_ID_C) ); create memory table T_VOCABULARY ( VOC_ID_C varchar(36) not null, VOC_NAME_C varchar(50) not null, VOC_VALUE_C varchar(500) not null, VOC_ORDER_N int not null, primary key (VOC_ID_C) );
create cached table T_CONTRIBUTOR ( CTR_ID_C varchar(36) not null, CTR_IDUSER_C varchar(36) not null, CTR_IDDOC_C varchar(36) not null, primary key (CTR_ID_C) );
insert into T_VOCABULARY(VOC_ID_C, VOC_NAME_C, VOC_VALUE_C, VOC_ORDER_N) values('type-collection', 'type', 'Collection', 0); insert into T_VOCABULARY(VOC_ID_C, VOC_NAME_C, VOC_VALUE_C, VOC_ORDER_N) values('type-collection', 'type', 'Collection', 0);
insert into T_VOCABULARY(VOC_ID_C, VOC_NAME_C, VOC_VALUE_C, VOC_ORDER_N) values('type-dataset', 'type', 'Dataset', 1); insert into T_VOCABULARY(VOC_ID_C, VOC_NAME_C, VOC_VALUE_C, VOC_ORDER_N) values('type-dataset', 'type', 'Dataset', 1);

View File

@ -40,12 +40,14 @@ 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.AclDao; import com.sismics.docs.core.dao.jpa.AclDao;
import com.sismics.docs.core.dao.jpa.ContributorDao;
import com.sismics.docs.core.dao.jpa.DocumentDao; import com.sismics.docs.core.dao.jpa.DocumentDao;
import com.sismics.docs.core.dao.jpa.FileDao; import com.sismics.docs.core.dao.jpa.FileDao;
import com.sismics.docs.core.dao.jpa.TagDao; import com.sismics.docs.core.dao.jpa.TagDao;
import com.sismics.docs.core.dao.jpa.UserDao; import com.sismics.docs.core.dao.jpa.UserDao;
import com.sismics.docs.core.dao.jpa.criteria.DocumentCriteria; import com.sismics.docs.core.dao.jpa.criteria.DocumentCriteria;
import com.sismics.docs.core.dao.jpa.dto.AclDto; import com.sismics.docs.core.dao.jpa.dto.AclDto;
import com.sismics.docs.core.dao.jpa.dto.ContributorDto;
import com.sismics.docs.core.dao.jpa.dto.DocumentDto; import com.sismics.docs.core.dao.jpa.dto.DocumentDto;
import com.sismics.docs.core.dao.jpa.dto.TagDto; import com.sismics.docs.core.dao.jpa.dto.TagDto;
import com.sismics.docs.core.event.DocumentCreatedAsyncEvent; import com.sismics.docs.core.event.DocumentCreatedAsyncEvent;
@ -159,6 +161,17 @@ public class DocumentResource extends BaseResource {
document.add("acls", aclList) document.add("acls", aclList)
.add("writable", writable); .add("writable", writable);
// Add contributors
ContributorDao contributorDao = new ContributorDao();
List<ContributorDto> contributorDtoList = contributorDao.getByDocumentId(documentId);
JsonArrayBuilder contributorList = Json.createArrayBuilder();
for (ContributorDto contributorDto : contributorDtoList) {
contributorList.add(Json.createObjectBuilder()
.add("username", contributorDto.getUsername())
.add("email", contributorDto.getEmail()));
}
document.add("contributors", contributorList);
return Response.ok().entity(document.build()).build(); return Response.ok().entity(document.build()).build();
} }

View File

@ -100,20 +100,36 @@ public class TestAclResource extends BaseJerseyTest {
acls = json.getJsonArray("acls"); acls = json.getJsonArray("acls");
Assert.assertEquals(4, acls.size()); Assert.assertEquals(4, acls.size());
// Update the document as acl2
json = target().path("/document/" + document1Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acl2Token)
.post(Entity.form(new Form()
.param("title", "My new super document 1")), JsonObject.class);
Assert.assertEquals(document1Id, json.getString("id"));
// Get the document as acl2
json = target().path("/document/" + document1Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acl2Token)
.get(JsonObject.class);
Assert.assertEquals(document1Id, json.getString("id"));
JsonArray contributors = json.getJsonArray("contributors");
Assert.assertEquals(2, contributors.size());
// Delete the ACL WRITE for acl2 with acl2 // Delete the ACL WRITE for acl2 with acl2
target().path("/acl/" + document1Id + "/WRITE/" + acl2Id).request() target().path("/acl/" + document1Id + "/WRITE/" + acl2Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acl2Token) .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acl2Token)
.delete(); .delete(JsonObject.class);
// Delete the ACL READ for acl2 with acl2 // Delete the ACL READ for acl2 with acl2 (not authorized)
target().path("/acl/" + document1Id + "/READ/" + acl2Id).request() response = target().path("/acl/" + document1Id + "/READ/" + acl2Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acl2Token) .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acl2Token)
.delete(); .delete();
Assert.assertEquals(Status.FORBIDDEN, Status.fromStatusCode(response.getStatus()));
// Delete the ACL READ for acl2 with acl1 // Delete the ACL READ for acl2 with acl1
target().path("/acl/" + document1Id + "/READ/" + acl2Id).request() target().path("/acl/" + document1Id + "/READ/" + acl2Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acl1Token) .cookie(TokenBasedSecurityFilter.COOKIE_NAME, acl1Token)
.delete(); .delete(JsonObject.class);
// Get the document as acl1 // Get the document as acl1
json = target().path("/document/" + document1Id).request() json = target().path("/document/" + document1Id).request()

View File

@ -222,6 +222,9 @@ public class TestDocumentResource extends BaseJerseyTest {
tags = json.getJsonArray("tags"); tags = json.getJsonArray("tags");
Assert.assertEquals(1, tags.size()); Assert.assertEquals(1, tags.size());
Assert.assertEquals(tag1Id, tags.getJsonObject(0).getString("id")); Assert.assertEquals(tag1Id, tags.getJsonObject(0).getString("id"));
JsonArray contributors = json.getJsonArray("contributors");
Assert.assertEquals(1, contributors.size());
Assert.assertEquals("document1", contributors.getJsonObject(0).getString("username"));
// Export a document in PDF format // Export a document in PDF format
Response response = target().path("/document/" + document1Id).request() Response response = target().path("/document/" + document1Id).request()
@ -279,6 +282,9 @@ public class TestDocumentResource extends BaseJerseyTest {
tags = json.getJsonArray("tags"); tags = json.getJsonArray("tags");
Assert.assertEquals(1, tags.size()); Assert.assertEquals(1, tags.size());
Assert.assertEquals(tag2Id, tags.getJsonObject(0).getString("id")); Assert.assertEquals(tag2Id, tags.getJsonObject(0).getString("id"));
contributors = json.getJsonArray("contributors");
Assert.assertEquals(1, contributors.size());
Assert.assertEquals("document1", contributors.getJsonObject(0).getString("username"));
// Deletes a document // Deletes a document
json = target().path("/document/" + document1Id).request() json = target().path("/document/" + document1Id).request()