Merge pull request #92 from sismics/tags_acl

Tags as source for ACL
This commit is contained in:
Benjamin Gamard 2016-05-08 01:11:32 +02:00
commit 1ed7422171
50 changed files with 1337 additions and 1010 deletions

11
.travis.yml Normal file
View File

@ -0,0 +1,11 @@
sudo: required
dist: trusty
language: java
before_install:
- sudo apt-get -qq update
- sudo apt-get -y -q install tesseract-ocr tesseract-ocr-fra tesseract-ocr-jpn
- sudo apt-get -y -q install haveged && sudo service haveged start
env:
global:
- TESSDATA_PREFIX=/usr/share/tesseract-ocr
- LC_NUMERIC=C

View File

@ -1,4 +1,4 @@
Sismics Docs
Sismics Docs [![Build Status](https://secure.travis-ci.org/sismics/docs.png)](http://travis-ci.org/sismics/docs)
============
_Web interface_
@ -50,7 +50,6 @@ Prerequisites: JDK 7 with JCE, Maven 3, Tesseract 3.02
Docs is organized in several Maven modules:
- docs-parent
- docs-core
- docs-web
- docs-web-common
@ -60,7 +59,7 @@ or download the sources from GitHub.
#### Launch the build
From the `docs-parent` directory:
From the root directory:
mvn clean -DskipTests install

View File

@ -6,7 +6,7 @@
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../docs-parent</relativePath>
<relativePath>..</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -28,7 +28,6 @@ public class AclDao {
* @param acl ACL
* @param userId User ID
* @return New ID
* @throws Exception
*/
public String create(Acl acl, String userId) {
// Create the UUID
@ -82,7 +81,7 @@ public class AclDao {
List<Object[]> l = q.getResultList();
// Assemble results
List<AclDto> aclDtoList = new ArrayList<AclDto>();
List<AclDto> aclDtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
AclDto aclDto = new AclDto();
@ -92,7 +91,7 @@ public class AclDao {
String userName = (String) o[i++];
String shareId = (String) o[i++];
String shareName = (String) o[i++];
String groupName = (String) o[i++];
String groupName = (String) o[i];
if (userName != null) {
aclDto.setTargetName(userName);
aclDto.setTargetType(AclTargetType.USER.name());
@ -114,23 +113,25 @@ public class AclDao {
* Check if a source is accessible to a target.
*
* @param sourceId ACL source entity ID
* @parm perm Necessary permission
* @param targetId ACL target entity ID
* @param perm Necessary permission
* @param targetIdList List of targets
* @return True if the document is accessible
*/
public boolean checkPermission(String sourceId, PermType perm, List<String> targetIdList) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select a from Acl a where a.sourceId = :sourceId and a.perm = :perm and a.targetId in (:targetIdList) and a.deleteDate is null");
StringBuilder sb = new StringBuilder("select a.ACL_ID_C from T_ACL a ");
sb.append(" where a.ACL_TARGETID_C in (:targetIdList) and a.ACL_SOURCEID_C = :sourceId and a.ACL_PERM_C = :perm and a.ACL_DELETEDATE_D is null ");
sb.append(" union all ");
sb.append(" select a.ACL_ID_C from T_ACL a, T_DOCUMENT_TAG dt ");
sb.append(" where a.ACL_SOURCEID_C = dt.DOT_IDTAG_C and dt.DOT_IDDOCUMENT_C = :sourceId and dt.DOT_DELETEDATE_D is null ");
sb.append(" and a.ACL_TARGETID_C in (:targetIdList) and a.ACL_PERM_C = :perm and a.ACL_DELETEDATE_D is null ");
Query q = em.createNativeQuery(sb.toString());
q.setParameter("sourceId", sourceId);
q.setParameter("perm", perm);
q.setParameter("perm", perm.name());
q.setParameter("targetIdList", targetIdList);
// We have a matching permission
if (q.getResultList().size() > 0) {
return true;
}
return false;
return q.getResultList().size() > 0;
}
/**

View File

@ -40,7 +40,6 @@ public class DocumentDao {
* @param document Document
* @param userId User ID
* @return New ID
* @throws Exception
*/
public String create(Document document, String userId) {
// Create the UUID
@ -87,10 +86,15 @@ public class DocumentDao {
*
* @param id Document ID
* @param perm Permission needed
* @param userId User ID
* @param targetIdList List of targets
* @return Document
*/
public DocumentDto getDocument(String id, PermType perm, List<String> targetIdList) {
AclDao aclDao = new AclDao();
if (!aclDao.checkPermission(id, perm, targetIdList)) {
return null;
}
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select distinct d.DOC_ID_C, d.DOC_TITLE_C, d.DOC_DESCRIPTION_C, d.DOC_SUBJECT_C, d.DOC_IDENTIFIER_C, d.DOC_PUBLISHER_C, d.DOC_FORMAT_C, d.DOC_SOURCE_C, d.DOC_TYPE_C, d.DOC_COVERAGE_C, d.DOC_RIGHTS_C, d.DOC_CREATEDATE_D, d.DOC_LANGUAGE_C, ");
sb.append(" (select count(s.SHA_ID_C) from T_SHARE s, T_ACL ac where ac.ACL_SOURCEID_C = d.DOC_ID_C and ac.ACL_TARGETID_C = s.SHA_ID_C and ac.ACL_DELETEDATE_D is null and s.SHA_DELETEDATE_D is null), ");
@ -98,15 +102,12 @@ public class DocumentDao {
sb.append(" u.USE_USERNAME_C ");
sb.append(" from T_DOCUMENT d ");
sb.append(" join T_USER u on d.DOC_IDUSER_C = u.USE_ID_C ");
sb.append(" left join T_ACL a on a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_TARGETID_C in (:targetIdList) and a.ACL_PERM_C = :perm and a.ACL_DELETEDATE_D is null ");
sb.append(" where d.DOC_ID_C = :id and a.ACL_ID_C is not null and d.DOC_DELETEDATE_D is null ");
sb.append(" where d.DOC_ID_C = :id and d.DOC_DELETEDATE_D is null ");
Query q = em.createNativeQuery(sb.toString());
q.setParameter("id", id);
q.setParameter("perm", perm.name());
q.setParameter("targetIdList", targetIdList);
Object[] o = null;
Object[] o;
try {
o = (Object[]) q.getSingleResult();
} catch (NoResultException e) {
@ -130,7 +131,7 @@ public class DocumentDao {
documentDto.setLanguage((String) o[i++]);
documentDto.setShared(((Number) o[i++]).intValue() > 0);
documentDto.setFileCount(((Number) o[i++]).intValue());
documentDto.setCreator((String) o[i++]);
documentDto.setCreator((String) o[i]);
return documentDto;
}
@ -200,22 +201,24 @@ public class DocumentDao {
* @param paginatedList List of documents (updated by side effects)
* @param criteria Search criteria
* @param sortCriteria Sort criteria
* @return List of documents
* @throws Exception
* @throws Exception
*/
public void findByCriteria(PaginatedList<DocumentDto> paginatedList, DocumentCriteria criteria, SortCriteria sortCriteria) throws Exception {
Map<String, Object> parameterMap = new HashMap<String, Object>();
List<String> criteriaList = new ArrayList<String>();
Map<String, Object> parameterMap = new HashMap<>();
List<String> criteriaList = new ArrayList<>();
StringBuilder sb = new StringBuilder("select distinct d.DOC_ID_C c0, d.DOC_TITLE_C c1, d.DOC_DESCRIPTION_C c2, d.DOC_CREATEDATE_D c3, d.DOC_LANGUAGE_C c4, ");
sb.append(" (select count(s.SHA_ID_C) from T_SHARE s, T_ACL ac where ac.ACL_SOURCEID_C = d.DOC_ID_C and ac.ACL_TARGETID_C = s.SHA_ID_C and ac.ACL_DELETEDATE_D is null and s.SHA_DELETEDATE_D is null) c5, ");
sb.append(" (select count(f.FIL_ID_C) from T_FILE f where f.FIL_DELETEDATE_D is null and f.FIL_IDDOC_C = d.DOC_ID_C) c6 ");
sb.append(" from T_DOCUMENT d ");
// Adds search criteria
// Add search criterias
if (criteria.getTargetIdList() != null) {
// Read permission is enough for searching
sb.append(" join T_ACL a on a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_TARGETID_C in (:targetIdList) and a.ACL_PERM_C = 'READ' and a.ACL_DELETEDATE_D is null ");
sb.append(" left join T_ACL a on a.ACL_TARGETID_C in (:targetIdList) and a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_PERM_C = 'READ' and a.ACL_DELETEDATE_D is null ");
sb.append(" left join T_DOCUMENT_TAG dta on dta.DOT_IDDOCUMENT_C = d.DOC_ID_C and dta.DOT_DELETEDATE_D is null ");
sb.append(" left join T_ACL a2 on a2.ACL_TARGETID_C in (:targetIdList) and a2.ACL_SOURCEID_C = dta.DOT_IDTAG_C and a2.ACL_PERM_C = 'READ' and a2.ACL_DELETEDATE_D is null ");
criteriaList.add("(a.ACL_ID_C is not null or a2.ACL_ID_C is not null)");
parameterMap.put("targetIdList", criteria.getTargetIdList());
}
if (!Strings.isNullOrEmpty(criteria.getSearch()) || !Strings.isNullOrEmpty(criteria.getFullSearch())) {
@ -239,7 +242,7 @@ public class DocumentDao {
if (criteria.getTagIdList() != null && !criteria.getTagIdList().isEmpty()) {
int index = 0;
for (String tagId : criteria.getTagIdList()) {
sb.append(" join T_DOCUMENT_TAG dt" + index + " on dt" + index + ".DOT_IDDOCUMENT_C = d.DOC_ID_C and dt" + index + ".DOT_IDTAG_C = :tagId" + index + " and dt" + index + ".DOT_DELETEDATE_D is null ");
sb.append(String.format(" join T_DOCUMENT_TAG dt%d on dt%d.DOT_IDDOCUMENT_C = d.DOC_ID_C and dt%d.DOT_IDTAG_C = :tagId%d and dt%d.DOT_DELETEDATE_D is null ", index, index, index, index, index));
parameterMap.put("tagId" + index, tagId);
index++;
}
@ -278,7 +281,7 @@ public class DocumentDao {
documentDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
documentDto.setLanguage((String) o[i++]);
documentDto.setShared(((Number) o[i++]).intValue() > 0);
documentDto.setFileCount(((Number) o[i++]).intValue());
documentDto.setFileCount(((Number) o[i]).intValue());
documentDtoList.add(documentDto);
}

View File

@ -71,7 +71,6 @@ public class GroupDao {
* @param group Group
* @param userId User ID
* @return New ID
* @throws Exception
*/
public String create(Group group, String userId) {
// Create the UUID
@ -127,9 +126,8 @@ public class GroupDao {
/**
* Add an user to a group.
*
* @param group Group
* @param userGroup User group
* @return New ID
* @throws Exception
*/
public String addMember(UserGroup userGroup) {
// Create the UUID
@ -170,8 +168,8 @@ public class GroupDao {
* @return List of groups
*/
public List<GroupDto> findByCriteria(GroupCriteria criteria, SortCriteria sortCriteria) {
Map<String, Object> parameterMap = new HashMap<String, Object>();
List<String> criteriaList = new ArrayList<String>();
Map<String, Object> parameterMap = new HashMap<>();
List<String> criteriaList = new ArrayList<>();
StringBuilder sb = new StringBuilder("select g.GRP_ID_C as c0, g.GRP_NAME_C as c1, g.GRP_IDPARENT_C as c2, gp.GRP_NAME_C as c3, g.GRP_IDROLE_C ");
if (criteria.getUserId() != null) {
@ -187,8 +185,8 @@ public class GroupDao {
}
if (criteria.getUserId() != null) {
// Left join and post-filtering for recursive groups
sb.append((criteria.isRecursive() ? " left " : "")
+ " join T_USER_GROUP ug on ug.UGP_IDGROUP_C = g.GRP_ID_C and ug.UGP_IDUSER_C = :userId and ug.UGP_DELETEDATE_D is null ");
sb.append(criteria.isRecursive() ? " left " : "");
sb.append(" join T_USER_GROUP ug on ug.UGP_IDGROUP_C = g.GRP_ID_C and ug.UGP_IDUSER_C = :userId and ug.UGP_DELETEDATE_D is null ");
parameterMap.put("userId", criteria.getUserId());
}
@ -216,7 +214,7 @@ public class GroupDao {
.setParentName((String) o[i++])
.setRoleId((String) o[i++]);
groupDtoList.add(groupDto);
if (criteria.getUserId() != null && o[i++] != null) {
if (criteria.getUserId() != null && o[i] != null) {
userGroupDtoList.add(groupDto);
}
}

View File

@ -1,21 +1,24 @@
package com.sismics.docs.core.dao.jpa;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import com.google.common.base.Joiner;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.dao.jpa.criteria.GroupCriteria;
import com.sismics.docs.core.dao.jpa.criteria.TagCriteria;
import com.sismics.docs.core.dao.jpa.dto.GroupDto;
import com.sismics.docs.core.dao.jpa.dto.TagDto;
import com.sismics.docs.core.dao.jpa.dto.TagStatDto;
import com.sismics.docs.core.model.jpa.DocumentTag;
import com.sismics.docs.core.model.jpa.Tag;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.docs.core.util.jpa.QueryParam;
import com.sismics.docs.core.util.jpa.QueryUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
/**
@ -39,19 +42,6 @@ public class TagDao {
}
}
/**
* Returns the list of all tags.
*
* @return List of tags
*/
@SuppressWarnings("unchecked")
public List<Tag> getByUserId(String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select t from Tag t where t.userId = :userId and t.deleteDate is null order by t.name");
q.setParameter("userId", userId);
return q.getResultList();
}
/**
* Update tags on a document.
*
@ -93,78 +83,6 @@ public class TagDao {
}
}
}
/**
* Returns tag list on a document.
*
* @param documentId Document ID
* @return List of tags
*/
@SuppressWarnings("unchecked")
public List<TagDto> getByDocumentId(String documentId, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select t.TAG_ID_C, t.TAG_NAME_C, t.TAG_COLOR_C, t.TAG_IDPARENT_C from T_DOCUMENT_TAG dt ");
sb.append(" join T_TAG t on t.TAG_ID_C = dt.DOT_IDTAG_C ");
sb.append(" where dt.DOT_IDDOCUMENT_C = :documentId and t.TAG_DELETEDATE_D is null ");
sb.append(" and t.TAG_IDUSER_C = :userId and dt.DOT_DELETEDATE_D is null ");
sb.append(" order by t.TAG_NAME_C ");
// Perform the query
Query q = em.createNativeQuery(sb.toString());
q.setParameter("documentId", documentId);
q.setParameter("userId", userId);
List<Object[]> l = q.getResultList();
// Assemble results
List<TagDto> tagDtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
TagDto tagDto = new TagDto();
tagDto.setId((String) o[i++]);
tagDto.setName((String) o[i++]);
tagDto.setColor((String) o[i++]);
tagDto.setParentId((String) o[i]);
tagDtoList.add(tagDto);
}
return tagDtoList;
}
/**
* Returns stats on tags.
*
* @param userId User ID
* @return Stats by tag
*/
@SuppressWarnings("unchecked")
public List<TagStatDto> getStats(String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select t.TAG_ID_C, t.TAG_NAME_C, t.TAG_COLOR_C, t.TAG_IDPARENT_C, count(d.DOC_ID_C) ");
sb.append(" from T_TAG t ");
sb.append(" left join T_DOCUMENT_TAG dt on t.TAG_ID_C = dt.DOT_IDTAG_C and dt.DOT_DELETEDATE_D is null ");
sb.append(" left join T_DOCUMENT d on d.DOC_ID_C = dt.DOT_IDDOCUMENT_C and d.DOC_DELETEDATE_D is null and d.DOC_IDUSER_C = :userId ");
sb.append(" where t.TAG_IDUSER_C = :userId and t.TAG_DELETEDATE_D is null ");
sb.append(" group by t.TAG_ID_C ");
sb.append(" order by t.TAG_NAME_C ");
// Perform the query
Query q = em.createNativeQuery(sb.toString());
q.setParameter("userId", userId);
List<Object[]> l = q.getResultList();
// Assemble results
List<TagStatDto> tagStatDtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
TagStatDto tagDto = new TagStatDto();
tagDto.setId((String) o[i++]);
tagDto.setName((String) o[i++]);
tagDto.setColor((String) o[i++]);
tagDto.setParentId((String) o[i++]);
tagDto.setCount(((Number) o[i]).intValue());
tagStatDtoList.add(tagDto);
}
return tagStatDtoList;
}
/**
* Creates a new tag.
@ -187,44 +105,6 @@ public class TagDao {
return tag.getId();
}
/**
* Returns a tag by name.
*
* @param userId User ID
* @param name Name
* @return Tag
*/
public Tag getByName(String userId, String name) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select t from Tag t where t.name = :name and t.userId = :userId and t.deleteDate is null");
q.setParameter("userId", userId);
q.setParameter("name", name);
try {
return (Tag) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Returns a tag by ID.
*
* @param userId User ID
* @param tagId Tag ID
* @return Tag
*/
public Tag getByTagId(String userId, String tagId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select t from Tag t where t.id = :tagId and t.userId = :userId and t.deleteDate is null");
q.setParameter("userId", userId);
q.setParameter("tagId", tagId);
try {
return (Tag) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Deletes a tag.
@ -249,25 +129,15 @@ public class TagDao {
q.setParameter("dateNow", dateNow);
q.setParameter("tagId", tagId);
q.executeUpdate();
q = em.createQuery("update Acl a set a.deleteDate = :dateNow where a.sourceId = :tagId and a.deleteDate is null");
q.setParameter("tagId", tagId);
q.setParameter("dateNow", dateNow);
q.executeUpdate();
// Create audit log
AuditLogUtil.create(tagDb, AuditLogType.DELETE, userId);
}
/**
* Search tags by name.
*
* @param name Tag name
* @return List of found tags
*/
@SuppressWarnings("unchecked")
public List<Tag> findByName(String userId, String name) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select t from Tag t where t.name like :name and t.userId = :userId and t.deleteDate is null");
q.setParameter("userId", userId);
q.setParameter("name", "%" + name + "%");
return q.getResultList();
}
/**
* Update a tag.
@ -294,5 +164,72 @@ public class TagDao {
return tagFromDb;
}
/**
* Returns the list of all tags.
*
* @param criteria Search criteria
* @param sortCriteria Sort criteria
* @return List of groups
*/
public List<TagDto> findByCriteria(TagCriteria criteria, SortCriteria sortCriteria) {
Map<String, Object> parameterMap = new HashMap<>();
List<String> criteriaList = new ArrayList<>();
StringBuilder sb = new StringBuilder("select distinct t.TAG_ID_C as c0, t.TAG_NAME_C as c1, t.TAG_COLOR_C as c2, t.TAG_IDPARENT_C as c3, u.USE_USERNAME_C as c4 ");
sb.append(" from T_TAG t ");
sb.append(" join T_USER u on t.TAG_IDUSER_C = u.USE_ID_C ");
// Add search criterias
if (criteria.getId() != null) {
criteriaList.add("t.TAG_ID_C = :id");
parameterMap.put("id", criteria.getId());
}
if (criteria.getTargetIdList() != null) {
sb.append(" left join T_ACL a on a.ACL_TARGETID_C in (:targetIdList) and a.ACL_SOURCEID_C = t.TAG_ID_C and a.ACL_PERM_C = 'READ' and a.ACL_DELETEDATE_D is null ");
criteriaList.add("a.ACL_ID_C is not null");
parameterMap.put("targetIdList", criteria.getTargetIdList());
}
if (criteria.getDocumentId() != null) {
sb.append(" join T_DOCUMENT_TAG dt on dt.DOT_IDTAG_C = t.TAG_ID_C and dt.DOT_DELETEDATE_D is null ");
criteriaList.add("dt.DOT_IDDOCUMENT_C = :documentId");
parameterMap.put("documentId", criteria.getDocumentId());
}
if (criteria.getName() != null) {
criteriaList.add("t.TAG_NAME_C = :name");
parameterMap.put("name", criteria.getName());
}
if (criteria.getNameLike() != null) {
criteriaList.add("t.TAG_NAME_C like :nameLike");
parameterMap.put("nameLike", "%" + criteria.getNameLike() + "%");
}
criteriaList.add("t.TAG_DELETEDATE_D is null");
if (!criteriaList.isEmpty()) {
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
}
// Perform the search
QueryParam queryParam = QueryUtil.getSortedQueryParam(new QueryParam(sb.toString(), parameterMap), sortCriteria);
@SuppressWarnings("unchecked")
List<Object[]> l = QueryUtil.getNativeQuery(queryParam).getResultList();
// Assemble results
List<TagDto> tagDtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
TagDto tagDto = new TagDto()
.setId((String) o[i++])
.setName((String) o[i++])
.setColor((String) o[i++])
.setParentId((String) o[i++])
.setCreator((String) o[i]);
tagDtoList.add(tagDto);
}
return tagDtoList;
}
}

View File

@ -188,23 +188,6 @@ public class UserDao {
}
}
/**
* Gets an active user by its password recovery token.
*
* @param passwordResetKey Password recovery token
* @return User
*/
public User getActiveByPasswordResetKey(String passwordResetKey) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
try {
Query q = em.createQuery("select u from User u where u.passwordResetKey = :passwordResetKey and u.deleteDate is null");
q.setParameter("passwordResetKey", passwordResetKey);
return (User) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Deletes a user.
*
@ -258,7 +241,7 @@ public class UserDao {
* @param password Clear password
* @return Hashed password
*/
protected String hashPassword(String password) {
private String hashPassword(String password) {
return BCrypt.hashpw(password, BCrypt.gensalt());
}
@ -270,8 +253,8 @@ public class UserDao {
* @return List of users
*/
public List<UserDto> findByCriteria(UserCriteria criteria, SortCriteria sortCriteria) {
Map<String, Object> parameterMap = new HashMap<String, Object>();
List<String> criteriaList = new ArrayList<String>();
Map<String, Object> parameterMap = new HashMap<>();
List<String> criteriaList = new ArrayList<>();
StringBuilder sb = new StringBuilder("select u.USE_ID_C as c0, u.USE_USERNAME_C as c1, u.USE_EMAIL_C as c2, u.USE_CREATEDATE_D as c3, u.USE_STORAGECURRENT_N as c4, u.USE_STORAGEQUOTA_N as c5");
sb.append(" from T_USER u ");
@ -300,7 +283,7 @@ public class UserDao {
List<Object[]> l = QueryUtil.getNativeQuery(queryParam).getResultList();
// Assemble results
List<UserDto> userDtoList = new ArrayList<UserDto>();
List<UserDto> userDtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
UserDto userDto = new UserDto();
@ -309,7 +292,7 @@ public class UserDao {
userDto.setEmail((String) o[i++]);
userDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
userDto.setStorageCurrent(((Number) o[i++]).longValue());
userDto.setStorageQuota(((Number) o[i++]).longValue());
userDto.setStorageQuota(((Number) o[i]).longValue());
userDtoList.add(userDto);
}
return userDtoList;

View File

@ -0,0 +1,80 @@
package com.sismics.docs.core.dao.jpa.criteria;
import java.util.List;
/**
* Tag criteria.
*
* @author bgamard
*/
public class TagCriteria {
/**
* Tag ID.
*/
private String id;
/**
* ACL target ID list.
*/
private List<String> targetIdList;
/**
* Document ID.
*/
private String documentId;
/**
* Tag name.
*/
private String name;
/**
* Approximate tag name.
*/
private String nameLike;
public String getId() {
return id;
}
public TagCriteria setId(String id) {
this.id = id;
return this;
}
public List<String> getTargetIdList() {
return targetIdList;
}
public TagCriteria setTargetIdList(List<String> targetIdList) {
this.targetIdList = targetIdList;
return this;
}
public String getDocumentId() {
return documentId;
}
public TagCriteria setDocumentId(String documentId) {
this.documentId = documentId;
return this;
}
public String getName() {
return name;
}
public TagCriteria setName(String name) {
this.name = name;
return this;
}
public String getNameLike() {
return nameLike;
}
public TagCriteria setNameLike(String nameLike) {
this.nameLike = nameLike;
return this;
}
}

View File

@ -26,35 +26,53 @@ public class TagDto {
*/
private String parentId;
/**
* Creator.
*/
private String creator;
public String getId() {
return id;
}
public void setId(String id) {
public TagDto setId(String id) {
this.id = id;
return this;
}
public String getName() {
return name;
}
public void setName(String name) {
public TagDto setName(String name) {
this.name = name;
return this;
}
public String getColor() {
return color;
}
public void setColor(String color) {
public TagDto setColor(String color) {
this.color = color;
return this;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
public TagDto setParentId(String parentId) {
this.parentId = parentId;
return this;
}
public String getCreator() {
return creator;
}
public TagDto setCreator(String creator) {
this.creator = creator;
return this;
}
}

View File

@ -37,27 +37,25 @@ import com.sismics.util.ResourceUtil;
*
* @author jtremeaux
*/
public abstract class DbOpenHelper {
abstract class DbOpenHelper {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(DbOpenHelper.class);
private final SqlStatementLogger sqlStatementLogger;
private final JdbcConnectionAccess jdbcConnectionAccess;
private final List<Exception> exceptions = new ArrayList<Exception>();
private final List<Exception> exceptions = new ArrayList<>();
private Formatter formatter;
private boolean haltOnError;
private Statement stmt;
public DbOpenHelper(ServiceRegistry serviceRegistry) throws HibernateException {
DbOpenHelper(ServiceRegistry serviceRegistry) throws HibernateException {
final JdbcServices jdbcServices = serviceRegistry.getService(JdbcServices.class);
sqlStatementLogger = jdbcServices.getSqlStatementLogger();
SqlStatementLogger sqlStatementLogger = jdbcServices.getSqlStatementLogger();
jdbcConnectionAccess = jdbcServices.getBootstrapJdbcConnectionAccess();
formatter = (sqlStatementLogger.isFormat() ? FormatStyle.DDL : FormatStyle.NONE).getFormatter();
}
@ -66,7 +64,6 @@ public abstract class DbOpenHelper {
log.info("Opening database and executing incremental updates");
Connection connection = null;
Writer outputFileWriter = null;
exceptions.clear();
@ -130,14 +127,6 @@ public abstract class DbOpenHelper {
exceptions.add(e);
log.error("Unable to close connection", e);
}
try {
if (outputFileWriter != null) {
outputFileWriter.close();
}
} catch (Exception e) {
exceptions.add(e);
log.error("Unable to close connection", e);
}
}
}
@ -147,7 +136,7 @@ public abstract class DbOpenHelper {
* @param version Version number
* @throws Exception
*/
protected void executeAllScript(final int version) throws Exception {
void executeAllScript(final int version) throws Exception {
List<String> fileNameList = ResourceUtil.list(getClass(), "/db/update/", new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
@ -173,7 +162,7 @@ public abstract class DbOpenHelper {
* @throws IOException
* @throws SQLException
*/
protected void executeScript(InputStream inputScript) throws IOException, SQLException {
void executeScript(InputStream inputScript) throws IOException, SQLException {
List<String> lines = CharStreams.readLines(new InputStreamReader(inputScript));
for (String sql : lines) {

View File

@ -97,10 +97,9 @@ public class TestFileUtil {
File file4 = new File();
file4.setId("document_odt");
file4.setMimeType(MimeType.OPEN_DOCUMENT_TEXT);
try (InputStream pdfInputStream = PdfUtil.convertToPdf(documentDto, Lists.newArrayList(file0, file1, file2, file3, file4), true, true, 10)) {
ByteStreams.copy(pdfInputStream, System.out);
}
InputStream is = PdfUtil.convertToPdf(documentDto, Lists.newArrayList(file0, file1, file2, file3, file4), true, true, 10);
is.close();
}
}
}

View File

@ -6,7 +6,7 @@
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../docs-parent</relativePath>
<relativePath>..</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -1,11 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../docs-parent</relativePath>
<relativePath>..</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -0,0 +1,40 @@
package com.sismics.rest.util;
import com.sismics.docs.core.constant.PermType;
import com.sismics.docs.core.dao.jpa.AclDao;
import com.sismics.docs.core.dao.jpa.dto.AclDto;
import com.sismics.security.IPrincipal;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import java.util.List;
/**
* Acl utilities.
*
* @author bgamard
*/
public class AclUtil {
/**
* Add ACLs to a JSON response.
*
* @param json JSON
* @param sourceId Source ID
* @param targetIdList List of target ID
*/
public static void addAcls(JsonObjectBuilder json, String sourceId, List<String> targetIdList) {
AclDao aclDao = new AclDao();
List<AclDto> aclDtoList = aclDao.getBySourceId(sourceId);
JsonArrayBuilder aclList = Json.createArrayBuilder();
for (AclDto aclDto : aclDtoList) {
aclList.add(Json.createObjectBuilder()
.add("perm", aclDto.getPerm().name())
.add("id", aclDto.getTargetId())
.add("name", JsonUtil.nullable(aclDto.getTargetName()))
.add("type", aclDto.getTargetType()));
}
json.add("acls", aclList)
.add("writable", aclDao.checkPermission(sourceId, PermType.WRITE, targetIdList));
}
}

View File

@ -9,7 +9,6 @@ import javax.json.JsonValue;
* @author bgamard
*/
public class JsonUtil {
/**
* Returns a JsonValue from a String.
*

View File

@ -80,7 +80,7 @@ public class RequestContextFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
EntityManager em = null;
EntityManager em;
try {
em = EMF.get().createEntityManager();

View File

@ -30,7 +30,7 @@ public abstract class BaseJerseyTest extends JerseyTest {
/**
* Test HTTP server.
*/
protected HttpServer httpServer;
private HttpServer httpServer;
/**
* Utility class for the REST client.
@ -45,7 +45,11 @@ public abstract class BaseJerseyTest extends JerseyTest {
@Override
protected Application configure() {
enable(TestProperties.LOG_TRAFFIC);
enable(TestProperties.DUMP_ENTITY);
String travisEnv = System.getenv("TRAVIS");
if (travisEnv == null || !travisEnv.equals("true")) {
// Travis don't like entity dumped in the logs
enable(TestProperties.DUMP_ENTITY);
}
return new Application();
}

View File

@ -6,7 +6,7 @@
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../docs-parent</relativePath>
<relativePath>..</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -1,40 +1,27 @@
package com.sismics.docs.rest.resource;
import java.text.MessageFormat;
import java.util.List;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import com.google.common.collect.Lists;
import com.sismics.docs.core.constant.AclTargetType;
import com.sismics.docs.core.constant.PermType;
import com.sismics.docs.core.dao.jpa.AclDao;
import com.sismics.docs.core.dao.jpa.DocumentDao;
import com.sismics.docs.core.dao.jpa.GroupDao;
import com.sismics.docs.core.dao.jpa.UserDao;
import com.sismics.docs.core.dao.jpa.*;
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.Acl;
import com.sismics.docs.core.model.jpa.Document;
import com.sismics.docs.core.model.jpa.Group;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.model.jpa.*;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.util.ValidationUtil;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import java.text.MessageFormat;
import java.util.List;
/**
* ACL REST resources.
*
@ -48,7 +35,7 @@ public class AclResource extends BaseResource {
* @param sourceId Source ID
* @param permStr Permission
* @param targetName Target name
* @param type ACL type
* @param typeStr ACL type
* @return Response
*/
@PUT
@ -156,7 +143,14 @@ public class AclResource extends BaseResource {
if (document != null && document.getUserId().equals(targetId)) {
throw new ClientException("AclError", "Cannot delete base ACL on a document");
}
// Cannot delete R/W on a source tag if the target is the creator
TagDao tagDao = new TagDao();
Tag tag = tagDao.getById(sourceId);
if (tag != null && tag.getUserId().equals(targetId)) {
throw new ClientException("AclError", "Cannot delete base ACL on a tag");
}
// Delete the ACL
aclDao.delete(sourceId, perm, targetId, principal.getId());

View File

@ -20,6 +20,13 @@ import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import com.sismics.docs.core.constant.PermType;
import com.sismics.docs.core.dao.jpa.AclDao;
import com.sismics.docs.core.dao.jpa.TagDao;
import com.sismics.docs.core.dao.jpa.criteria.TagCriteria;
import com.sismics.docs.core.dao.jpa.dto.AclDto;
import com.sismics.docs.core.dao.jpa.dto.TagDto;
import com.sismics.docs.core.model.jpa.Acl;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Appender;
import org.apache.log4j.Level;
@ -284,12 +291,12 @@ public class AppResource extends BaseResource {
Map<String, User> userMap = new HashMap<>();
for (File file : fileList) {
java.nio.file.Path storedFile = DirectoryUtil.getStorageDirectory().resolve(file.getId());
User user = null;
User user;
if (userMap.containsKey(file.getUserId())) {
user = userMap.get(file.getUserId());
} else {
user = userDao.getById(file.getUserId());
user.setStorageCurrent(0l);
user.setStorageCurrent(0L);
userMap.put(user.getId(), user);
}
@ -312,4 +319,49 @@ public class AppResource extends BaseResource {
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Add base ACLs to tags.
*
* @return Response
*/
@POST
@Path("batch/tag_acls")
public Response batchTagAcls() {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Get all tags
TagDao tagDao = new TagDao();
List<TagDto> tagDtoList = tagDao.findByCriteria(new TagCriteria(), null);
// Add READ and WRITE ACLs
for (TagDto tagDto : tagDtoList) {
AclDao aclDao = new AclDao();
List<AclDto> aclDtoList = aclDao.getBySourceId(tagDto.getId());
if (aclDtoList.size() == 0) {
// Create read ACL
Acl acl = new Acl();
acl.setPerm(PermType.READ);
acl.setSourceId(tagDto.getId());
acl.setTargetId(principal.getId());
aclDao.create(acl, principal.getId());
// Create write ACL
acl = new Acl();
acl.setPerm(PermType.WRITE);
acl.setSourceId(tagDto.getId());
acl.setTargetId(principal.getId());
aclDao.create(acl, principal.getId());
}
}
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
}

View File

@ -1,14 +1,5 @@
package com.sismics.docs.rest.resource;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import com.google.common.base.Strings;
import com.sismics.docs.core.constant.PermType;
import com.sismics.docs.core.dao.jpa.AclDao;
@ -21,6 +12,15 @@ import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.util.JsonUtil;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
/**
* Audit log REST resources.
*
@ -50,7 +50,7 @@ public class AuditLogResource extends BaseResource {
// Check ACL on the document
AclDao aclDao = new AclDao();
if (!aclDao.checkPermission(documentId, PermType.READ, getTargetIdList(null))) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
criteria.setDocumentId(documentId);
}

View File

@ -1,20 +1,5 @@
package com.sismics.docs.rest.resource;
import java.util.List;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import com.sismics.docs.core.constant.PermType;
import com.sismics.docs.core.dao.jpa.CommentDao;
import com.sismics.docs.core.dao.jpa.DocumentDao;
@ -24,6 +9,13 @@ import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.util.ValidationUtil;
import com.sismics.util.ImageUtil;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import java.util.List;
/**
* Comment REST resource.
*
@ -52,7 +44,7 @@ public class CommentResource extends BaseResource {
// Read access on doc gives access to write comments
DocumentDao documentDao = new DocumentDao();
if (documentDao.getDocument(documentId, PermType.READ, getTargetIdList(null)) == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
// Create the comment
@ -90,7 +82,7 @@ public class CommentResource extends BaseResource {
CommentDao commentDao = new CommentDao();
Comment comment = commentDao.getActiveById(id);
if (comment == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
// If the current user owns the comment, skip ACL check
@ -98,7 +90,7 @@ public class CommentResource extends BaseResource {
// Get the associated document
DocumentDao documentDao = new DocumentDao();
if (documentDao.getDocument(comment.getDocumentId(), PermType.WRITE, getTargetIdList(null)) == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
}
@ -126,7 +118,7 @@ public class CommentResource extends BaseResource {
// Read access on doc gives access to read comments
DocumentDao documentDao = new DocumentDao();
if (documentDao.getDocument(documentId, PermType.READ, getTargetIdList(shareId)) == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
// Assemble results

View File

@ -1,56 +1,14 @@
package com.sismics.docs.rest.resource;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.StreamingOutput;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;
import org.joda.time.format.DateTimeParser;
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.AclDao;
import com.sismics.docs.core.dao.jpa.ContributorDao;
import com.sismics.docs.core.dao.jpa.DocumentDao;
import com.sismics.docs.core.dao.jpa.FileDao;
import com.sismics.docs.core.dao.jpa.RelationDao;
import com.sismics.docs.core.dao.jpa.TagDao;
import com.sismics.docs.core.dao.jpa.UserDao;
import com.sismics.docs.core.dao.jpa.*;
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.ContributorDto;
import com.sismics.docs.core.dao.jpa.dto.DocumentDto;
import com.sismics.docs.core.dao.jpa.dto.RelationDto;
import com.sismics.docs.core.dao.jpa.dto.TagDto;
import com.sismics.docs.core.dao.jpa.criteria.TagCriteria;
import com.sismics.docs.core.dao.jpa.dto.*;
import com.sismics.docs.core.event.DocumentCreatedAsyncEvent;
import com.sismics.docs.core.event.DocumentDeletedAsyncEvent;
import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent;
@ -59,7 +17,6 @@ import com.sismics.docs.core.model.context.AppContext;
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.Tag;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.util.PdfUtil;
import com.sismics.docs.core.util.jpa.PaginatedList;
@ -68,9 +25,27 @@ import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.exception.ServerException;
import com.sismics.rest.util.AclUtil;
import com.sismics.rest.util.JsonUtil;
import com.sismics.rest.util.ValidationUtil;
import com.sismics.util.mime.MimeType;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;
import org.joda.time.format.DateTimeParser;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
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.*;
/**
* Document REST resources.
@ -83,6 +58,7 @@ public class DocumentResource extends BaseResource {
* Returns a document.
*
* @param documentId Document ID
* @param shareId Share ID
* @return Response
*/
@GET
@ -93,10 +69,9 @@ public class DocumentResource extends BaseResource {
authenticate();
DocumentDao documentDao = new DocumentDao();
AclDao aclDao = new AclDao();
DocumentDto documentDto = documentDao.getDocument(documentId, PermType.READ, getTargetIdList(shareId));
if (documentDto == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
JsonObjectBuilder document = Json.createObjectBuilder()
@ -114,7 +89,11 @@ public class DocumentResource extends BaseResource {
} else {
// Add tags added by the current user on this document
TagDao tagDao = new TagDao();
List<TagDto> tagDtoList = tagDao.getByDocumentId(documentId, principal.getId());
List<TagDto> tagDtoList = tagDao.findByCriteria(
new TagCriteria()
.setTargetIdList(getTargetIdList(shareId))
.setDocumentId(documentId),
new SortCriteria(1, true));
JsonArrayBuilder tags = Json.createArrayBuilder();
for (TagDto tagDto : tagDtoList) {
tags.add(Json.createObjectBuilder()
@ -135,28 +114,9 @@ public class DocumentResource extends BaseResource {
document.add("coverage", JsonUtil.nullable(documentDto.getCoverage()));
document.add("rights", JsonUtil.nullable(documentDto.getRights()));
document.add("creator", documentDto.getCreator());
// Add ACL
List<AclDto> aclDtoList = aclDao.getBySourceId(documentId);
JsonArrayBuilder aclList = Json.createArrayBuilder();
boolean writable = false;
for (AclDto aclDto : aclDtoList) {
aclList.add(Json.createObjectBuilder()
.add("perm", aclDto.getPerm().name())
.add("id", aclDto.getTargetId())
.add("name", JsonUtil.nullable(aclDto.getTargetName()))
.add("type", aclDto.getTargetType()));
if (!principal.isAnonymous()
&& (aclDto.getTargetId().equals(principal.getId())
|| principal.getGroupIdSet().contains(aclDto.getTargetId()))
&& aclDto.getPerm() == PermType.WRITE) {
// The document is writable for the current user
writable = true;
}
}
document.add("acls", aclList)
.add("writable", writable);
AclUtil.addAcls(document, documentId, getTargetIdList(shareId));
// Add contributors
ContributorDao contributorDao = new ContributorDao();
@ -188,6 +148,11 @@ public class DocumentResource extends BaseResource {
* Export a document to PDF.
*
* @param documentId Document ID
* @param shareId Share ID
* @param metadata Export metadata
* @param comments Export comments
* @param fitImageToPage Fit images to page
* @param marginStr Margins
* @return Response
*/
@GET
@ -208,7 +173,7 @@ public class DocumentResource extends BaseResource {
DocumentDao documentDao = new DocumentDao();
final DocumentDto documentDto = documentDao.getDocument(documentId, PermType.READ, getTargetIdList(shareId));
if (documentDto == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
// Get files
@ -231,7 +196,11 @@ public class DocumentResource extends BaseResource {
} catch (Exception e) {
throw new IOException(e);
} finally {
outputStream.close();
try {
outputStream.close();
} catch (IOException e) {
// Ignore
}
}
}
};
@ -247,6 +216,9 @@ public class DocumentResource extends BaseResource {
*
* @param limit Page limit
* @param offset Page offset
* @param sortColumn Sort column
* @param asc Sorting
* @param search Search query
* @return Response
*/
@GET
@ -278,7 +250,7 @@ public class DocumentResource extends BaseResource {
for (DocumentDto documentDto : paginatedList.getResultList()) {
// Get tags added by the current user on this document
List<TagDto> tagDtoList = tagDao.getByDocumentId(documentDto.getId(), principal.getId());
List<TagDto> tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)).setDocumentId(documentDto.getId()), new SortCriteria(1, true));
JsonArrayBuilder tags = Json.createArrayBuilder();
for (TagDto tagDto : tagDtoList) {
tags.add(Json.createObjectBuilder()
@ -337,77 +309,87 @@ public class DocumentResource extends BaseResource {
query.add(criteria);
continue;
}
if (params[0].equals("tag")) {
// New tag criteria
List<Tag> tagList = tagDao.findByName(principal.getId(), params[1]);
if (documentCriteria.getTagIdList() == null) {
documentCriteria.setTagIdList(new ArrayList<String>());
}
if (tagList.size() == 0) {
// No tag found, the request must returns nothing
documentCriteria.getTagIdList().add(UUID.randomUUID().toString());
}
for (Tag tag : tagList) {
documentCriteria.getTagIdList().add(tag.getId());
}
} else if (params[0].equals("after") || params[0].equals("before")) {
// New date span criteria
try {
DateTime date = formatter.parseDateTime(params[1]);
if (params[0].equals("before")) documentCriteria.setCreateDateMax(date.toDate());
else documentCriteria.setCreateDateMin(date.toDate());
} catch (IllegalArgumentException e) {
// Invalid date, returns no documents
if (params[0].equals("before")) documentCriteria.setCreateDateMax(new Date(0));
else documentCriteria.setCreateDateMin(new Date(Long.MAX_VALUE / 2));
}
} else if (params[0].equals("at")) {
// New specific date criteria
try {
if (params[1].length() == 10) {
DateTime date = dayFormatter.parseDateTime(params[1]);
documentCriteria.setCreateDateMin(date.toDate());
documentCriteria.setCreateDateMax(date.plusDays(1).minusSeconds(1).toDate());
} else if (params[1].length() == 7) {
DateTime date = monthFormatter.parseDateTime(params[1]);
documentCriteria.setCreateDateMin(date.toDate());
documentCriteria.setCreateDateMax(date.plusMonths(1).minusSeconds(1).toDate());
} else if (params[1].length() == 4) {
DateTime date = yearFormatter.parseDateTime(params[1]);
documentCriteria.setCreateDateMin(date.toDate());
documentCriteria.setCreateDateMax(date.plusYears(1).minusSeconds(1).toDate());
switch (params[0]) {
case "tag":
// New tag criteria
List<TagDto> tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)).setNameLike(params[1]), null);
if (documentCriteria.getTagIdList() == null) {
documentCriteria.setTagIdList(new ArrayList<String>());
}
} catch (IllegalArgumentException e) {
// Invalid date, returns no documents
documentCriteria.setCreateDateMin(new Date(0));
documentCriteria.setCreateDateMax(new Date(0));
}
} else if (params[0].equals("shared")) {
// New shared state criteria
if (params[1].equals("yes")) {
documentCriteria.setShared(true);
}
} else if (params[0].equals("lang")) {
// New language criteria
if (Constants.SUPPORTED_LANGUAGES.contains(params[1])) {
documentCriteria.setLanguage(params[1]);
}
} else if (params[0].equals("by")) {
// New creator criteria
User user = userDao.getActiveByUsername(params[1]);
if (user == null) {
// This user doesn't exists, return nothing
documentCriteria.setCreatorId(UUID.randomUUID().toString());
} else {
// This user exists, search its documents
documentCriteria.setCreatorId(user.getId());
}
} else if (params[0].equals("full")) {
// New full content search criteria
fullQuery.add(params[1]);
} else {
query.add(criteria);
if (tagDtoList.size() == 0) {
// No tag found, the request must returns nothing
documentCriteria.getTagIdList().add(UUID.randomUUID().toString());
}
for (TagDto tagDto : tagDtoList) {
documentCriteria.getTagIdList().add(tagDto.getId());
}
break;
case "after":
case "before":
// New date span criteria
try {
DateTime date = formatter.parseDateTime(params[1]);
if (params[0].equals("before")) documentCriteria.setCreateDateMax(date.toDate());
else documentCriteria.setCreateDateMin(date.toDate());
} catch (IllegalArgumentException e) {
// Invalid date, returns no documents
if (params[0].equals("before")) documentCriteria.setCreateDateMax(new Date(0));
else documentCriteria.setCreateDateMin(new Date(Long.MAX_VALUE / 2));
}
break;
case "at":
// New specific date criteria
try {
if (params[1].length() == 10) {
DateTime date = dayFormatter.parseDateTime(params[1]);
documentCriteria.setCreateDateMin(date.toDate());
documentCriteria.setCreateDateMax(date.plusDays(1).minusSeconds(1).toDate());
} else if (params[1].length() == 7) {
DateTime date = monthFormatter.parseDateTime(params[1]);
documentCriteria.setCreateDateMin(date.toDate());
documentCriteria.setCreateDateMax(date.plusMonths(1).minusSeconds(1).toDate());
} else if (params[1].length() == 4) {
DateTime date = yearFormatter.parseDateTime(params[1]);
documentCriteria.setCreateDateMin(date.toDate());
documentCriteria.setCreateDateMax(date.plusYears(1).minusSeconds(1).toDate());
}
} catch (IllegalArgumentException e) {
// Invalid date, returns no documents
documentCriteria.setCreateDateMin(new Date(0));
documentCriteria.setCreateDateMax(new Date(0));
}
break;
case "shared":
// New shared state criteria
if (params[1].equals("yes")) {
documentCriteria.setShared(true);
}
break;
case "lang":
// New language criteria
if (Constants.SUPPORTED_LANGUAGES.contains(params[1])) {
documentCriteria.setLanguage(params[1]);
}
break;
case "by":
// New creator criteria
User user = userDao.getActiveByUsername(params[1]);
if (user == null) {
// This user doesn't exists, return nothing
documentCriteria.setCreatorId(UUID.randomUUID().toString());
} else {
// This user exists, search its documents
documentCriteria.setCreatorId(user.getId());
}
break;
case "full":
// New full content search criteria
fullQuery.add(params[1]);
break;
default:
query.add(criteria);
break;
}
}
@ -421,7 +403,16 @@ public class DocumentResource extends BaseResource {
*
* @param title Title
* @param description Description
* @param tags Tags
* @param subject Subject
* @param identifier Identifier
* @param publisher Publisher
* @param format Format
* @param source Source
* @param type Type
* @param coverage Coverage
* @param rights Rights
* @param tagList Tags
* @param relationList Relations
* @param language Language
* @param createDateStr Creation date
* @return Response
@ -573,7 +564,7 @@ public class DocumentResource extends BaseResource {
DocumentDao documentDao = new DocumentDao();
Document document = documentDao.getById(id);
if (document == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
// Update the document
@ -594,7 +585,7 @@ public class DocumentResource extends BaseResource {
document.setCreateDate(createDate);
}
document = documentDao.update(document, principal.getId());
documentDao.update(document, principal.getId());
// Update tags
updateTagList(id, tagList);
@ -624,9 +615,9 @@ public class DocumentResource extends BaseResource {
TagDao tagDao = new TagDao();
Set<String> tagSet = new HashSet<>();
Set<String> tagIdSet = new HashSet<>();
List<Tag> tagDbList = tagDao.getByUserId(principal.getId());
for (Tag tagDb : tagDbList) {
tagIdSet.add(tagDb.getId());
List<TagDto> tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)), null);
for (TagDto tagDto : tagDtoList) {
tagIdSet.add(tagDto.getId());
}
for (String tagId : tagList) {
if (!tagIdSet.contains(tagId)) {
@ -679,7 +670,7 @@ public class DocumentResource extends BaseResource {
FileDao fileDao = new FileDao();
DocumentDto documentDto = documentDao.getDocument(id, PermType.WRITE, getTargetIdList(null));
if (documentDto == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
List<File> fileList = fileDao.getByDocumentId(principal.getId(), id);

View File

@ -16,17 +16,7 @@ import java.util.zip.ZipOutputStream;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
@ -100,7 +90,7 @@ public class FileResource extends BaseResource {
DocumentDao documentDao = new DocumentDao();
documentDto = documentDao.getDocument(documentId, PermType.WRITE, getTargetIdList(null));
if (documentDto == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
}
@ -215,7 +205,7 @@ public class FileResource extends BaseResource {
File file = fileDao.getFile(id, principal.getId());
DocumentDto documentDto = documentDao.getDocument(documentId, PermType.WRITE, getTargetIdList(null));
if (file == null || documentDto == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
// Check that the file is orphan
@ -277,7 +267,7 @@ public class FileResource extends BaseResource {
// Get the document
DocumentDao documentDao = new DocumentDao();
if (documentDao.getDocument(documentId, PermType.WRITE, getTargetIdList(null)) == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
// Reorder files
@ -313,7 +303,7 @@ public class FileResource extends BaseResource {
if (documentId != null) {
AclDao aclDao = new AclDao();
if (!aclDao.checkPermission(documentId, PermType.READ, getTargetIdList(shareId))) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
} else if (!authenticated) {
throw new ForbiddenClientException();
@ -360,7 +350,7 @@ public class FileResource extends BaseResource {
DocumentDao documentDao = new DocumentDao();
File file = fileDao.getFile(id);
if (file == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
DocumentDto documentDto = null;
@ -371,7 +361,7 @@ public class FileResource extends BaseResource {
throw new ForbiddenClientException();
}
} else if ((documentDto = documentDao.getDocument(file.getDocumentId(), PermType.WRITE, getTargetIdList(null))) == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
// Delete the file
@ -433,7 +423,7 @@ public class FileResource extends BaseResource {
UserDao userDao = new UserDao();
File file = fileDao.getFile(fileId);
if (file == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
if (file.getDocumentId() == null) {
@ -454,7 +444,7 @@ public class FileResource extends BaseResource {
// Get the stored file
java.nio.file.Path storedFile;
String mimeType;
boolean decrypt = false;
boolean decrypt;
if (size != null) {
storedFile = DirectoryUtil.getStorageDirectory().resolve(fileId + "_" + size);
mimeType = MimeType.IMAGE_JPEG; // Thumbnails are JPEG
@ -488,8 +478,12 @@ public class FileResource extends BaseResource {
try {
ByteStreams.copy(responseInputStream, outputStream);
} finally {
responseInputStream.close();
outputStream.close();
try {
responseInputStream.close();
outputStream.close();
} catch (IOException e) {
// Ignore
}
}
}
};
@ -521,7 +515,7 @@ public class FileResource extends BaseResource {
DocumentDao documentDao = new DocumentDao();
DocumentDto documentDto = documentDao.getDocument(documentId, PermType.READ, getTargetIdList(shareId));
if (documentDto == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
// Get files and user associated with this document

View File

@ -1,22 +1,5 @@
package com.sismics.docs.rest.resource;
import java.text.MessageFormat;
import java.util.List;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import com.google.common.base.Strings;
import com.sismics.docs.core.dao.jpa.GroupDao;
import com.sismics.docs.core.dao.jpa.UserDao;
@ -34,6 +17,14 @@ import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.util.JsonUtil;
import com.sismics.rest.util.ValidationUtil;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import java.text.MessageFormat;
import java.util.List;
/**
* Group REST resources.
*
@ -109,12 +100,12 @@ public class GroupResource extends BaseResource {
GroupDao groupDao = new GroupDao();
Group group = groupDao.getActiveByName(groupName);
if (group == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
// Avoid duplicates
Group existingGroup = groupDao.getActiveByName(name);
if (existingGroup != null && existingGroup.getId() != group.getId()) {
if (existingGroup != null && !existingGroup.getId().equals(group.getId())) {
throw new ClientException("GroupAlreadyExists", MessageFormat.format("This group already exists: {0}", name));
}
@ -155,7 +146,7 @@ public class GroupResource extends BaseResource {
GroupDao groupDao = new GroupDao();
Group group = groupDao.getActiveByName(groupName);
if (group == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
// Delete the group
@ -191,14 +182,14 @@ public class GroupResource extends BaseResource {
GroupDao groupDao = new GroupDao();
Group group = groupDao.getActiveByName(groupName);
if (group == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
// Get the user
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(username);
if (user == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
// Avoid duplicates
@ -248,14 +239,14 @@ public class GroupResource extends BaseResource {
GroupDao groupDao = new GroupDao();
Group group = groupDao.getActiveByName(groupName);
if (group == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
// Get the user
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(username);
if (user == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
// Remove the membership
@ -315,7 +306,7 @@ public class GroupResource extends BaseResource {
GroupDao groupDao = new GroupDao();
Group group = groupDao.getActiveByName(groupName);
if (group == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
// Build the response

View File

@ -1,19 +1,6 @@
package com.sismics.docs.rest.resource;
import java.text.MessageFormat;
import java.util.List;
import javax.json.Json;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import com.sismics.docs.core.constant.AclTargetType;
import com.sismics.docs.core.constant.PermType;
import com.sismics.docs.core.dao.jpa.AclDao;
@ -26,6 +13,13 @@ import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.util.JsonUtil;
import com.sismics.rest.util.ValidationUtil;
import javax.json.Json;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import java.text.MessageFormat;
import java.util.List;
/**
* Share REST resources.
*
@ -55,7 +49,7 @@ public class ShareResource extends BaseResource {
// Get the document
DocumentDao documentDao = new DocumentDao();
if (documentDao.getDocument(documentId, PermType.WRITE, getTargetIdList(null)) == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
// Create the share

View File

@ -1,29 +1,28 @@
package com.sismics.docs.rest.resource;
import java.text.MessageFormat;
import java.util.List;
import com.google.common.collect.Sets;
import com.sismics.docs.core.constant.PermType;
import com.sismics.docs.core.dao.jpa.AclDao;
import com.sismics.docs.core.dao.jpa.TagDao;
import com.sismics.docs.core.dao.jpa.criteria.TagCriteria;
import com.sismics.docs.core.dao.jpa.dto.TagDto;
import com.sismics.docs.core.model.jpa.Acl;
import com.sismics.docs.core.model.jpa.Tag;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.util.AclUtil;
import com.sismics.rest.util.ValidationUtil;
import org.apache.commons.lang.StringUtils;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import org.apache.commons.lang.StringUtils;
import com.sismics.docs.core.dao.jpa.TagDao;
import com.sismics.docs.core.dao.jpa.dto.TagStatDto;
import com.sismics.docs.core.model.jpa.Tag;
import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.util.JsonUtil;
import com.sismics.rest.util.ValidationUtil;
import java.text.MessageFormat;
import java.util.List;
import java.util.Set;
/**
* Tag REST resources.
@ -33,7 +32,7 @@ import com.sismics.rest.util.ValidationUtil;
@Path("/tag")
public class TagResource extends BaseResource {
/**
* Returns the list of all tags.
* Returns the list of all visible tags.
*
* @return Response
*/
@ -45,54 +44,71 @@ public class TagResource extends BaseResource {
}
TagDao tagDao = new TagDao();
List<Tag> tagList = tagDao.getByUserId(principal.getId());
List<TagDto> tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)), new SortCriteria(1, true));
// Extract tag IDs
Set<String> tagIdSet = Sets.newHashSet();
for (TagDto tagDto : tagDtoList) {
tagIdSet.add(tagDto.getId());
}
// Build the response
JsonArrayBuilder items = Json.createArrayBuilder();
for (Tag tag : tagList) {
items.add(Json.createObjectBuilder()
.add("id", tag.getId())
.add("name", tag.getName())
.add("color", tag.getColor())
.add("parent", JsonUtil.nullable(tag.getParentId())));
for (TagDto tagDto : tagDtoList) {
JsonObjectBuilder item = Json.createObjectBuilder()
.add("id", tagDto.getId())
.add("name", tagDto.getName())
.add("color", tagDto.getColor());
if (tagIdSet.contains(tagDto.getParentId())) {
item.add("parent", tagDto.getParentId());
}
items.add(item);
}
JsonObjectBuilder response = Json.createObjectBuilder()
.add("tags", items);
return Response.ok().entity(response.build()).build();
}
/**
* Returns stats on tags.
*
* Returns a tag.
*
* @param id Tag ID
* @return Response
*/
@GET
@Path("/stats")
public Response stats() {
@Path("{id: [a-z0-9\\-]+}")
public Response get(@PathParam("id") String id) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
TagDao tagDao = new TagDao();
List<TagStatDto> tagStatDtoList = tagDao.getStats(principal.getId());
JsonArrayBuilder items = Json.createArrayBuilder();
for (TagStatDto tagStatDto : tagStatDtoList) {
items.add(Json.createObjectBuilder()
.add("id", tagStatDto.getId())
.add("name", tagStatDto.getName())
.add("color", tagStatDto.getColor())
.add("parent", JsonUtil.nullable(tagStatDto.getParentId()))
.add("count", tagStatDto.getCount()));
List<TagDto> tagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)).setId(id), null);
if (tagDtoList.size() == 0) {
throw new NotFoundException();
}
JsonObjectBuilder response = Json.createObjectBuilder()
.add("stats", items);
return Response.ok().entity(response.build()).build();
// Add tag informatiosn
TagDto tagDto = tagDtoList.get(0);
JsonObjectBuilder tag = Json.createObjectBuilder()
.add("id", tagDto.getId())
.add("creator", tagDto.getCreator())
.add("name", tagDto.getName())
.add("color", tagDto.getColor());
// Add ACL
AclUtil.addAcls(tag, id, getTargetIdList(null));
return Response.ok().entity(tag.build()).build();
}
/**
* Creates a new tag.
*
* @param name Name
* @param color Color
* @param parentId Parent ID
* @return Response
*/
@PUT
@ -113,30 +129,39 @@ public class TagResource extends BaseResource {
throw new ClientException("SpacesNotAllowed", "Spaces are not allowed in tag name");
}
// Get the tag
TagDao tagDao = new TagDao();
Tag tag = tagDao.getByName(principal.getId(), name);
if (tag != null) {
throw new ClientException("AlreadyExistingTag", MessageFormat.format("Tag already exists: {0}", name));
}
// Check the parent
if (StringUtils.isEmpty(parentId)) {
parentId = null;
} else {
Tag parentTag = tagDao.getByTagId(principal.getId(), parentId);
if (parentTag == null) {
AclDao aclDao = new AclDao();
if (!aclDao.checkPermission(parentId, PermType.READ, getTargetIdList(null))) {
throw new ClientException("ParentNotFound", MessageFormat.format("Parent not found: {0}", parentId));
}
}
// Create the tag
tag = new Tag();
TagDao tagDao = new TagDao();
Tag tag = new Tag();
tag.setName(name);
tag.setColor(color);
tag.setUserId(principal.getId());
tag.setParentId(parentId);
String id = tagDao.create(tag, principal.getId());
// Create read ACL
AclDao aclDao = new AclDao();
Acl acl = new Acl();
acl.setPerm(PermType.READ);
acl.setSourceId(id);
acl.setTargetId(principal.getId());
aclDao.create(acl, principal.getId());
// Create write ACL
acl = new Acl();
acl.setPerm(PermType.WRITE);
acl.setSourceId(id);
acl.setTargetId(principal.getId());
aclDao.create(acl, principal.getId());
JsonObjectBuilder response = Json.createObjectBuilder()
.add("id", id);
@ -147,6 +172,8 @@ public class TagResource extends BaseResource {
* Update a tag.
*
* @param name Name
* @param color Color
* @param parentId Parent ID
* @return Response
*/
@POST
@ -169,30 +196,24 @@ public class TagResource extends BaseResource {
throw new ClientException("SpacesNotAllowed", "Spaces are not allowed in tag name");
}
// Get the tag
TagDao tagDao = new TagDao();
Tag tag = tagDao.getByTagId(principal.getId(), id);
if (tag == null) {
throw new ClientException("TagNotFound", MessageFormat.format("Tag not found: {0}", id));
// Check permission
AclDao aclDao = new AclDao();
if (!aclDao.checkPermission(id, PermType.WRITE, getTargetIdList(null))) {
throw new NotFoundException();
}
// Check the parent
if (StringUtils.isEmpty(parentId)) {
parentId = null;
} else {
Tag parentTag = tagDao.getByTagId(principal.getId(), parentId);
if (parentTag == null) {
if (!aclDao.checkPermission(parentId, PermType.READ, getTargetIdList(null))) {
throw new ClientException("ParentNotFound", MessageFormat.format("Parent not found: {0}", parentId));
}
}
// Check for name duplicate
Tag tagDuplicate = tagDao.getByName(principal.getId(), name);
if (tagDuplicate != null && !tagDuplicate.getId().equals(id)) {
throw new ClientException("AlreadyExistingTag", MessageFormat.format("Tag already exists: {0}", name));
}
// Update the tag
TagDao tagDao = new TagDao();
Tag tag = tagDao.getById(id);
if (!StringUtils.isEmpty(name)) {
tag.setName(name);
}
@ -212,26 +233,26 @@ public class TagResource extends BaseResource {
/**
* Delete a tag.
*
* @param tagId Tag ID
* @param id Tag ID
* @return Response
*/
@DELETE
@Path("{id: [a-z0-9\\-]+}")
public Response delete(
@PathParam("id") String tagId) {
@PathParam("id") String id) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Get the tag
TagDao tagDao = new TagDao();
Tag tag = tagDao.getByTagId(principal.getId(), tagId);
if (tag == null) {
throw new ClientException("TagNotFound", MessageFormat.format("Tag not found: {0}", tagId));
AclDao aclDao = new AclDao();
if (!aclDao.checkPermission(id, PermType.WRITE, getTargetIdList(null))) {
throw new NotFoundException();
}
// Delete the tag
tagDao.delete(tagId, principal.getId());
TagDao tagDao = new TagDao();
tagDao.delete(id, principal.getId());
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()

View File

@ -99,7 +99,7 @@ public class UserResource extends BaseResource {
user.setPassword(password);
user.setEmail(email);
user.setStorageQuota(storageQuota);
user.setStorageCurrent(0l);
user.setStorageCurrent(0L);
try {
user.setPrivateKey(EncryptionUtil.generatePrivateKey());
} catch (NoSuchAlgorithmException e) {
@ -678,7 +678,7 @@ public class UserResource extends BaseResource {
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(principal.getName());
user.setTotpKey(key.getKey());
user = userDao.update(user, principal.getId());
userDao.update(user, principal.getId());
JsonObjectBuilder response = Json.createObjectBuilder()
.add("secret", key.getKey());

View File

@ -1,26 +1,18 @@
package com.sismics.docs.rest.resource;
import java.util.List;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import com.sismics.docs.core.dao.jpa.VocabularyDao;
import com.sismics.docs.core.model.jpa.Vocabulary;
import com.sismics.docs.rest.constant.BaseFunction;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.util.ValidationUtil;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import java.util.List;
/**
* Vocabulary REST resources.
*
@ -58,7 +50,7 @@ public class VocabularyResource extends BaseResource {
*
* @param name Name
* @param value Value
* @param order Order
* @param orderStr Order
* @return Response
*/
@PUT
@ -95,10 +87,11 @@ public class VocabularyResource extends BaseResource {
/**
* Update a vocabulary.
*
*
* @param id ID
* @param name Name
* @param value Value
* @param order Order
* @param orderStr Order
* @return Response
*/
@POST
@ -127,7 +120,7 @@ public class VocabularyResource extends BaseResource {
VocabularyDao vocabularyDao = new VocabularyDao();
Vocabulary vocabulary = vocabularyDao.getById(id);
if (vocabulary == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
// Update the vocabulary
@ -169,7 +162,7 @@ public class VocabularyResource extends BaseResource {
VocabularyDao vocabularyDao = new VocabularyDao();
Vocabulary vocabulary = vocabularyDao.getById(id);
if (vocabulary == null) {
return Response.status(Status.NOT_FOUND).build();
throw new NotFoundException();
}
// Delete the vocabulary

View File

@ -13,36 +13,55 @@ angular.module('docs',
* Configuring modules.
*/
.config(function($stateProvider, $httpProvider, RestangularProvider) {
// Configuring UI Router
$stateProvider
.state('main', {
url: '',
views: {
'page': {
templateUrl: 'partial/docs/main.html',
controller: 'Main'
.state('main', {
url: '',
views: {
'page': {
templateUrl: 'partial/docs/main.html',
controller: 'Main'
}
}
}
})
.state('tag', {
url: '/tag',
views: {
'page': {
templateUrl: 'partial/docs/tag.html',
controller: 'Tag'
})
.state('tag', {
url: '/tag',
abstract: true,
views: {
'page': {
templateUrl: 'partial/docs/tag.html',
controller: 'Tag'
}
}
}
})
.state('settings', {
url: '/settings',
abstract: true,
views: {
'page': {
templateUrl: 'partial/docs/settings.html',
controller: 'Settings'
})
.state('tag.default', {
url: '',
views: {
'tag': {
templateUrl: 'partial/docs/tag.default.html'
}
}
}
})
})
.state('tag.edit', {
url: '/:id',
views: {
'tag': {
templateUrl: 'partial/docs/tag.edit.html',
controller: 'TagEdit'
}
}
})
.state('settings', {
url: '/settings',
abstract: true,
views: {
'page': {
templateUrl: 'partial/docs/settings.html',
controller: 'Settings'
}
}
})
.state('settings.default', {
url: '',
views: {
@ -106,70 +125,70 @@ angular.module('docs',
}
}
})
.state('settings.user.edit', {
url: '/edit/:username',
views: {
'user': {
templateUrl: 'partial/docs/settings.user.edit.html',
controller: 'SettingsUserEdit'
}
.state('settings.user.edit', {
url: '/edit/:username',
views: {
'user': {
templateUrl: 'partial/docs/settings.user.edit.html',
controller: 'SettingsUserEdit'
}
})
.state('settings.user.add', {
url: '/add',
views: {
'user': {
templateUrl: 'partial/docs/settings.user.edit.html',
controller: 'SettingsUserEdit'
}
}
})
.state('settings.user.add', {
url: '/add',
views: {
'user': {
templateUrl: 'partial/docs/settings.user.edit.html',
controller: 'SettingsUserEdit'
}
})
}
})
.state('settings.group', {
url: '/group',
views: {
'settings': {
templateUrl: 'partial/docs/settings.group.html',
controller: 'SettingsGroup'
}
url: '/group',
views: {
'settings': {
templateUrl: 'partial/docs/settings.group.html',
controller: 'SettingsGroup'
}
})
.state('settings.group.edit', {
url: '/edit/:name',
views: {
'group': {
templateUrl: 'partial/docs/settings.group.edit.html',
controller: 'SettingsGroupEdit'
}
}
})
.state('settings.group.add', {
url: '/add',
views: {
'group': {
templateUrl: 'partial/docs/settings.group.edit.html',
controller: 'SettingsGroupEdit'
}
}
})
.state('settings.vocabulary', {
url: '/vocabulary',
views: {
'settings': {
templateUrl: 'partial/docs/settings.vocabulary.html',
controller: 'SettingsVocabulary'
}
}
})
.state('document', {
url: '/document',
abstract: true,
views: {
'page': {
templateUrl: 'partial/docs/document.html',
controller: 'Document'
})
.state('settings.group.edit', {
url: '/edit/:name',
views: {
'group': {
templateUrl: 'partial/docs/settings.group.edit.html',
controller: 'SettingsGroupEdit'
}
}
}
})
})
.state('settings.group.add', {
url: '/add',
views: {
'group': {
templateUrl: 'partial/docs/settings.group.edit.html',
controller: 'SettingsGroupEdit'
}
}
})
.state('settings.vocabulary', {
url: '/vocabulary',
views: {
'settings': {
templateUrl: 'partial/docs/settings.vocabulary.html',
controller: 'SettingsVocabulary'
}
}
})
.state('document', {
url: '/document',
abstract: true,
views: {
'page': {
templateUrl: 'partial/docs/document.html',
controller: 'Document'
}
}
})
.state('document.default', {
url: '',
views: {
@ -179,17 +198,17 @@ angular.module('docs',
}
}
})
.state('document.default.search', {
url: '/search/:search'
})
.state('document.default.file', {
url: '/file/:fileId',
views: {
'file': {
controller: 'FileView'
}
.state('document.default.search', {
url: '/search/:search'
})
.state('document.default.file', {
url: '/file/:fileId',
views: {
'file': {
controller: 'FileView'
}
})
}
})
.state('document.add', {
url: '/add?files',
views: {
@ -218,59 +237,68 @@ angular.module('docs',
}
}
})
.state('document.view.content', {
url: '/content',
views: {
'tab': {
templateUrl: 'partial/docs/document.view.content.html',
controller: 'DocumentViewContent'
}
.state('document.view.content', {
url: '/content',
views: {
'tab': {
templateUrl: 'partial/docs/document.view.content.html',
controller: 'DocumentViewContent'
}
})
.state('document.view.content.file', {
url: '/file/:fileId',
views: {
'file': {
controller: 'FileView'
}
}
})
.state('document.view.permissions', {
url: '/permissions',
views: {
'tab': {
templateUrl: 'partial/docs/document.view.permissions.html',
controller: 'DocumentViewPermissions'
}
}
})
.state('document.view.activity', {
url: '/activity',
views: {
'tab': {
templateUrl: 'partial/docs/document.view.activity.html',
controller: 'DocumentViewActivity'
}
}
})
.state('login', {
url: '/login',
views: {
'page': {
templateUrl: 'partial/docs/login.html',
controller: 'Login'
}
}
})
.state('user', {
url: '/user',
views: {
'page': {
templateUrl: 'partial/docs/usergroup.html',
controller: 'UserGroup'
})
.state('document.view.content.file', {
url: '/file/:fileId',
views: {
'file': {
controller: 'FileView'
}
}
}
})
})
.state('document.view.permissions', {
url: '/permissions',
views: {
'tab': {
templateUrl: 'partial/docs/document.view.permissions.html',
controller: 'DocumentViewPermissions'
}
}
})
.state('document.view.activity', {
url: '/activity',
views: {
'tab': {
templateUrl: 'partial/docs/document.view.activity.html',
controller: 'DocumentViewActivity'
}
}
})
.state('login', {
url: '/login',
views: {
'page': {
templateUrl: 'partial/docs/login.html',
controller: 'Login'
}
}
})
.state('user', {
url: '/user',
abstract: true,
views: {
'page': {
templateUrl: 'partial/docs/usergroup.html',
controller: 'UserGroup'
}
}
})
.state('user.default', {
url: '',
views: {
'sub': {
templateUrl: 'partial/docs/usergroup.default.html'
}
}
})
.state('user.profile', {
url: '/:username',
views: {
@ -280,15 +308,24 @@ angular.module('docs',
}
}
})
.state('group', {
url: '/group',
views: {
'page': {
templateUrl: 'partial/docs/usergroup.html',
controller: 'UserGroup'
.state('group', {
url: '/group',
abstract: true,
views: {
'page': {
templateUrl: 'partial/docs/usergroup.html',
controller: 'UserGroup'
}
}
}
})
})
.state('group.default', {
url: '',
views: {
'sub': {
templateUrl: 'partial/docs/usergroup.default.html'
}
}
})
.state('group.profile', {
url: '/:name',
views: {
@ -298,7 +335,6 @@ angular.module('docs',
}
}
});
// Configuring Restangular
RestangularProvider.setBaseUrl('../api');

View File

@ -3,95 +3,5 @@
/**
* Document view permissions controller.
*/
angular.module('docs').controller('DocumentViewPermissions', function ($scope, $stateParams, Restangular, $q) {
// Watch for ACLs change and group them for easy displaying
$scope.$watch('document.acls', function(acls) {
$scope.acls = _.groupBy(acls, function(acl) {
return acl.id;
});
});
// Initialize add ACL
$scope.acl = { perm: 'READ' };
/**
* Delete an ACL.
*/
$scope.deleteAcl = function(acl) {
Restangular.one('acl/' + $stateParams.id + '/' + acl.perm + '/' + acl.id, null).remove().then(function () {
$scope.document.acls = _.reject($scope.document.acls, function(s) {
return angular.equals(acl, s);
});
});
};
/**
* Add an ACL.
*/
$scope.addAcl = function() {
// Compute ACLs to add
$scope.acl.source = $stateParams.id;
var acls = [];
if ($scope.acl.perm == 'READWRITE') {
acls = [{
source: $stateParams.id,
target: $scope.acl.target.name,
perm: 'READ',
type: $scope.acl.target.type
}, {
source: $stateParams.id,
target: $scope.acl.target.name,
perm: 'WRITE',
type: $scope.acl.target.type
}];
} else {
acls = [{
source: $stateParams.id,
target: $scope.acl.target.name,
perm: $scope.acl.perm,
type: $scope.acl.target.type
}];
}
// Add ACLs
_.each(acls, function(acl) {
Restangular.one('acl').put(acl).then(function(acl) {
if (_.isUndefined(acl.id)) {
return;
}
$scope.document.acls.push(acl);
$scope.document.acls = angular.copy($scope.document.acls);
});
});
// Reset form
$scope.acl = { perm: 'READ' };
};
/**
* Auto-complete on ACL target.
*/
$scope.getTargetAclTypeahead = function($viewValue) {
var deferred = $q.defer();
Restangular.one('acl/target/search')
.get({
search: $viewValue
}).then(function(data) {
var output = [];
// Add the type to use later
output.push.apply(output, _.map(data.users, function(user) {
user.type = 'USER';
return user;
}));
output.push.apply(output, _.map(data.groups, function(group) {
group.type = 'GROUP';
return group;
}));
// Send the data to the typeahead directive
deferred.resolve(output, true);
});
return deferred.promise;
};
angular.module('docs').controller('DocumentViewPermissions', function() {
});

View File

@ -3,37 +3,14 @@
/**
* Tag controller.
*/
angular.module('docs').controller('Tag', function($scope, $dialog, Tag, Restangular) {
angular.module('docs').controller('Tag', function($scope, $dialog, Restangular) {
$scope.tag = { name: '', color: '#3a87ad' };
// Retrieve tags
Tag.tags().then(function(data) {
Restangular.one('tag/list').get().then(function(data) {
$scope.tags = data.tags;
});
// Retrieve tag stats
Restangular.one('tag/stats').get().then(function(data) {
$scope.stats = data.stats;
});
/**
* Returns total number of document from tag stats.
*/
$scope.getStatCount = function() {
return _.reduce($scope.stats, function(memo, stat) {
return memo + stat.count
}, 0);
};
/**
* Validate a tag name for duplicate.
*/
$scope.validateDuplicate = function(name) {
return !_.find($scope.tags, function(tag) {
return tag.name == name;
});
};
/**
* Add a tag.
*/
@ -71,15 +48,6 @@ angular.module('docs').controller('Tag', function($scope, $dialog, Tag, Restangu
*/
$scope.updateTag = function(tag) {
// Update the server
return Restangular.one('tag', tag.id).post('', tag).then(function () {
// Update the stat object
var stat = _.find($scope.stats, function (t) {
return tag.id == t.id;
});
if (stat) {
_.extend(stat, tag);
}
});
return Restangular.one('tag', tag.id).post('', tag);
};
});

View File

@ -0,0 +1,10 @@
'use strict';
/**
* Tag edit controller.
*/
angular.module('docs').controller('TagEdit', function($scope, $stateParams, Restangular) {
Restangular.one('tag', $stateParams.id).get().then(function(data) {
$scope.tag = data;
})
});

View File

@ -0,0 +1,112 @@
'use strict';
/**
* ACL edit directive.
*/
angular.module('docs').directive('aclEdit', function() {
return {
restrict: 'E',
templateUrl: 'partial/docs/directive.acledit.html',
replace: true,
scope: {
source: '=',
acls: '=',
writable: '=',
creator: '='
},
controller: function($scope, Restangular, $q) {
// Watch for ACLs change and group them for easy displaying
$scope.$watch('acls', function(acls) {
$scope.groupedAcls = _.groupBy(acls, function(acl) {
return acl.id;
});
});
// Initialize add ACL
$scope.acl = { perm: 'READ' };
/**
* Delete an ACL.
*/
$scope.deleteAcl = function(acl) {
Restangular.one('acl/' + $scope.source + '/' + acl.perm + '/' + acl.id, null).remove().then(function () {
$scope.acls = _.reject($scope.acls, function(s) {
return angular.equals(acl, s);
});
});
};
/**
* Add an ACL.
*/
$scope.addAcl = function() {
// Compute ACLs to add
$scope.acl.source = $scope.source;
var acls = [];
if ($scope.acl.perm == 'READWRITE') {
acls = [{
source: $scope.source,
target: $scope.acl.target.name,
perm: 'READ',
type: $scope.acl.target.type
}, {
source: $scope.source,
target: $scope.acl.target.name,
perm: 'WRITE',
type: $scope.acl.target.type
}];
} else {
acls = [{
source: $scope.source,
target: $scope.acl.target.name,
perm: $scope.acl.perm,
type: $scope.acl.target.type
}];
}
// Add ACLs
_.each(acls, function(acl) {
Restangular.one('acl').put(acl).then(function(acl) {
if (_.isUndefined(acl.id)) {
return;
}
$scope.acls.push(acl);
$scope.acls = angular.copy($scope.acls);
});
});
// Reset form
$scope.acl = { perm: 'READ' };
};
/**
* Auto-complete on ACL target.
*/
$scope.getTargetAclTypeahead = function($viewValue) {
var deferred = $q.defer();
Restangular.one('acl/target/search')
.get({
search: $viewValue
}).then(function(data) {
var output = [];
// Add the type to use later
output.push.apply(output, _.map(data.users, function(user) {
user.type = 'USER';
return user;
}));
output.push.apply(output, _.map(data.groups, function(group) {
group.type = 'GROUP';
return group;
}));
// Send the data to the typeahead directive
deferred.resolve(output, true);
});
return deferred.promise;
};
},
link: function(scope, element, attr, ctrl) {
}
}
});

View File

@ -13,9 +13,9 @@ angular.module('docs').directive('selectTag', function() {
ref: '@',
ngDisabled: '='
},
controller: function($scope, Tag) {
controller: function($scope, Restangular) {
// Retrieve tags
Tag.tags().then(function(data) {
Restangular.one('tag/list').get().then(function(data) {
$scope.allTags = data.tags;
});
@ -48,7 +48,7 @@ angular.module('docs').directive('selectTag', function() {
if ($event) {
$event.preventDefault();
}
}
};
/**
* Remove a tag.

View File

@ -1,18 +0,0 @@
'use strict';
/**
* Tag service.
*/
angular.module('docs').factory('Tag', function(Restangular) {
var tags = null;
return {
/**
* Returns tags.
* @param force If true, force reloading data
*/
tags: function(force) {
return Restangular.one('tag/list').get();
}
}
});

View File

@ -55,6 +55,7 @@
<script src="app/docs/controller/document/FileModalView.js" type="text/javascript"></script>
<script src="app/docs/controller/Login.js" type="text/javascript"></script>
<script src="app/docs/controller/tag/Tag.js" type="text/javascript"></script>
<script src="app/docs/controller/tag/TagEdit.js" type="text/javascript"></script>
<script src="app/docs/controller/Navigation.js" type="text/javascript"></script>
<script src="app/docs/controller/settings/Settings.js" type="text/javascript"></script>
<script src="app/docs/controller/settings/SettingsDefault.js" type="text/javascript"></script>
@ -73,7 +74,6 @@
<script src="app/docs/controller/usergroup/UserProfile.js" type="text/javascript"></script>
<script src="app/docs/controller/usergroup/GroupProfile.js" type="text/javascript"></script>
<script src="app/docs/service/User.js" type="text/javascript"></script>
<script src="app/docs/service/Tag.js" type="text/javascript"></script>
<script src="app/docs/filter/Newline.js" type="text/javascript"></script>
<script src="app/docs/filter/Shorten.js" type="text/javascript"></script>
<script src="app/docs/filter/Filesize.js" type="text/javascript"></script>
@ -84,6 +84,7 @@
<script src="app/docs/directive/InlineEdit.js" type="text/javascript"></script>
<script src="app/docs/directive/ImgError.js" type="text/javascript"></script>
<script src="app/docs/directive/Acl.js" type="text/javascript"></script>
<script src="app/docs/directive/AclEdit.js" type="text/javascript"></script>
<!-- endref -->
</head>
<body>

View File

@ -0,0 +1,61 @@
<div>
<table class="table">
<tr>
<th style="width: 40%">For</th>
<th style="width: 40%">Permission</th>
</tr>
<tr ng-repeat="(id, acl) in groupedAcls">
<td><acl data="acl[0]"></acl></td>
<td>
<span class="label label-default" style="margin-right: 6px;" ng-repeat="a in acl | orderBy: 'perm'">
{{ a.perm }}
<span ng-show="(creator != a.name && a.type == 'USER' || a.type != 'USER') && writable"
class="glyphicon glyphicon-remove pointer"
ng-click="deleteAcl(a)"></span>
</span>
</td>
</tr>
</table>
<div ng-show="writable">
<h4>Add a permission</h4>
<form name="aclForm" class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label" for="inputTarget">For</label>
<div class="col-sm-3">
<input required ng-maxlength="50" class="form-control" type="text" id="inputTarget"
placeholder="Search a user or group" name="target" ng-model="acl.target" autocomplete="off"
typeahead="target as target.name for target in getTargetAclTypeahead($viewValue) | filter: $viewValue"
typeahead-template-url="partial/docs/directive.typeahead.acl.html"
typeahead-wait-ms="200" />
</div>
<div class="col-sm-4">
<span class="btn btn-primary" ng-if="acl.target.type" ng-click="acl.target = null">
<acl data="acl.target"></acl>
</span>
</div>
</div>
<div class="form-group">
<label class=" col-sm-2 control-label" for="inputPermission">Permission</label>
<div class="col-sm-3">
<select class="form-control" ng-model="acl.perm" id="inputPermission">
<option value="READ">Can read</option>
<option value="READWRITE">Can edit</option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary" ng-disabled="!acl.target.type" ng-click="addAcl()">
<span class="glyphicon glyphicon-plus"></span>
Add
</button>
</div>
</div>
</form>
</div>
</div>

View File

@ -1,59 +1,4 @@
<table class="table">
<tr>
<th style="width: 40%">For</th>
<th style="width: 40%">Permission</th>
</tr>
<tr ng-repeat="(id, acl) in acls">
<td><acl data="acl[0]"></acl></td>
<td>
<span class="label label-default" style="margin-right: 6px;" ng-repeat="a in acl | orderBy: 'perm'">
{{ a.perm }}
<span ng-show="(document.creator != a.name && a.type == 'USER' || a.type != 'USER') && document.writable"
class="glyphicon glyphicon-remove pointer"
ng-click="deleteAcl(a)"></span>
</span>
</td>
</tr>
</table>
<div ng-show="document.writable">
<h4>Add a permission</h4>
<form name="aclForm" class="form-horizontal">
<div class="form-group">
<label class="col-sm-2 control-label" for="inputTarget">For</label>
<div class="col-sm-3">
<input required ng-maxlength="50" class="form-control" type="text" id="inputTarget"
placeholder="Search a user or group" name="target" ng-model="acl.target" autocomplete="off"
typeahead="target as target.name for target in getTargetAclTypeahead($viewValue) | filter: $viewValue"
typeahead-template-url="partial/docs/directive.typeahead.acl.html"
typeahead-wait-ms="200" />
</div>
<div class="col-sm-4">
<span class="btn btn-primary" ng-if="acl.target.type" ng-click="acl.target = null">
<acl data="acl.target"></acl>
</span>
</div>
</div>
<div class="form-group">
<label class=" col-sm-2 control-label" for="inputPermission">Permission</label>
<div class="col-sm-3">
<select class="form-control" ng-model="acl.perm" id="inputPermission">
<option value="READ">Can read</option>
<option value="READWRITE">Can edit</option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary" ng-disabled="!acl.target.type" ng-click="addAcl()">
<span class="glyphicon glyphicon-plus"></span>
Add
</button>
</div>
</div>
</form>
</div>
<acl-edit source="document.id"
acls="document.acls"
writable="document.writable"
creator="document.creator"></acl-edit>

View File

@ -0,0 +1,6 @@
<h1>Tags</h1>
<p><strong>Tags</strong> are labels associated to documents.</p>
<p>A document can be tagged by multiple tags, and a tag can be applied to multiple documents.</p>
<p>Using the <span class="glyphicon glyphicon-user"></span> button, you can edit permissions on a tag.</p>
<p>If a tag can be read by another user or group, associated documents can also be read by those people.</p>
<p>For example, tag your company documents with a tag <span class="label label-info">MyCompany</span> and add the permission <strong>Read</strong> to a group <span class="btn btn-default">employees</span></p>

View File

@ -0,0 +1,4 @@
<acl-edit source="tag.id"
acls="tag.acls"
writable="tag.writable"
creator="tag.creator"></acl-edit>

View File

@ -5,10 +5,9 @@
<p class="input-group" ng-class="{ 'has-error': !tagForm.name.$valid }">
<span colorpicker class="input-group-addon btn btn-default" data-color="#3a87ad" ng-model="tag.color" ng-style="{ 'background': tag.color }">&nbsp;</span>
<input type="text" name="name" placeholder="New tag" class="form-control"
ng-maxlength="36" required ng-model="tag.name" ui-validate="{duplicate: 'validateDuplicate($value)', space: '!$value || $value.indexOf(\' \') == -1' }">
ng-maxlength="36" required ng-model="tag.name" ui-validate="{ space: '!$value || $value.indexOf(\' \') == -1' }">
<span class="input-group-addon btn btn-primary" ng-disabled="!tagForm.$valid" ng-click="addTag()">Add</span>
</p>
<span class="help-block" ng-show="tagForm.name.$error.duplicate">This tag already exists</span>
<span class="help-block" ng-show="tagForm.name.$error.space">Space are not allowed</span>
</form>
@ -31,22 +30,15 @@
</select>
</td>
<td class="col-xs-1"><span colorpicker class="btn" on-hide="updateTag(tag)" data-color="" ng-model="tag.color" ng-style="{ 'background': tag.color }">&nbsp;</span></td>
<td class="col-xs-1"><button class="btn btn-danger pull-right" ng-click="deleteTag(tag)"><span class="glyphicon glyphicon-trash"></span></button></td>
<td class="col-xs-1"><a href="#/tag/{{ tag.id }}" class="btn btn-default pull-right" title="Edit permissions"><span class="glyphicon glyphicon-user"></span></a></td>
<td class="col-xs-1"><button class="btn btn-danger pull-right" ng-click="deleteTag(tag)" title="Delete this tag"><span class="glyphicon glyphicon-trash"></span></button></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="col-md-8" ng-if="stats.length >= 0">
<h1>{{ tags.length }} <small>tag{{ tags.length > 1 ? 's' : '' }}</small></h1>
<dl class="dl-horizontal" ng-repeat="stat in stats | orderBy: '-count'">
<dt>{{ stat.name }} <span class="badge badge-info" ng-style="{ 'background': stat.color }">{{ stat.count }}</span></dt>
<dd><progressbar value="stat.count / getStatCount() * 100" class="progress-info"></progressbar></dd>
</dl>
</div>
<div class="col-md-8" ng-if="!stats">
<img src="img/loader.gif" />
<div class="col-md-8">
<div ui-view="tag"></div>
</div>
</div>

View File

@ -0,0 +1,2 @@
<h1>Users & Groups</h1>
<p>Here you can view informations about users and groups.</p>

View File

@ -71,7 +71,7 @@ public class TestAclResource extends BaseJerseyTest {
String acl2Id = json.getString("id");
// Add an ACL WRITE for acl2 with acl1
json = target().path("/acl").request()
target().path("/acl").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acl1Token)
.put(Entity.form(new Form()
.param("source", document1Id)
@ -80,7 +80,7 @@ public class TestAclResource extends BaseJerseyTest {
.param("type", "USER")), JsonObject.class);
// Add an ACL WRITE for acl2 with acl1 (again)
json = target().path("/acl").request()
target().path("/acl").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acl1Token)
.put(Entity.form(new Form()
.param("source", document1Id)
@ -99,7 +99,7 @@ public class TestAclResource extends BaseJerseyTest {
String aclGroup2Id = json.getString("id");
// Add an ACL WRITE for aclGroup2 with acl1
json = target().path("/acl").request()
target().path("/acl").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acl1Token)
.put(Entity.form(new Form()
.param("source", document1Id)
@ -234,9 +234,9 @@ public class TestAclResource extends BaseJerseyTest {
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acl1Token)
.get(JsonObject.class);
JsonArray users = json.getJsonArray("users");
Assert.assertEquals(2, users.size());
Assert.assertTrue(users.size() > 0);
JsonArray groups = json.getJsonArray("groups");
Assert.assertEquals(1, groups.size());
Assert.assertTrue(groups.size() > 0);
// Search target list (admin)
json = target().path("/acl/target/search")
@ -249,4 +249,179 @@ public class TestAclResource extends BaseJerseyTest {
groups = json.getJsonArray("groups");
Assert.assertEquals(1, groups.size());
}
@Test
public void testAclTags() {
// Login acltag1
clientUtil.createUser("acltag1");
String acltag1Token = clientUtil.login("acltag1");
// Login acltag2
clientUtil.createUser("acltag2");
String acltag2Token = clientUtil.login("acltag2");
// Create tag1 with acltag1
JsonObject json = target().path("/tag").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag1Token)
.put(Entity.form(new Form()
.param("name", "AclTag1")
.param("color", "#ff0000")), JsonObject.class);
String tag1Id = json.getString("id");
Assert.assertNotNull(tag1Id);
// Create document1 with acltag1 tagged with tag1
json = target().path("/document").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag1Token)
.put(Entity.form(new Form()
.param("title", "My super document 1")
.param("tags", tag1Id)
.param("language", "eng")), JsonObject.class);
String document1Id = json.getString("id");
// acltag2 cannot see document1
Response response = target().path("/document/" + document1Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token)
.get();
Assert.assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
// acltag2 cannot see any tag
json = target().path("/tag/list").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token)
.get(JsonObject.class);
JsonArray tags = json.getJsonArray("tags");
Assert.assertEquals(0, tags.size());
// acltag2 cannot see tag1
response = target().path("/tag/" + tag1Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token)
.get();
Assert.assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
// acltag2 cannot see any document
json = target().path("/document/list")
.queryParam("sort_column", 3)
.queryParam("asc", true)
.request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token)
.get(JsonObject.class);
JsonArray documents = json.getJsonArray("documents");
Assert.assertEquals(0, documents.size());
// acltag2 cannot edit tag1
response = target().path("/tag/" + tag1Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token)
.post(Entity.form(new Form()
.param("name", "AclTag1")
.param("color", "#ff0000")));
Assert.assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
// acltag2 cannot edit document1
response = target().path("/document/" + document1Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token)
.post(Entity.form(new Form()
.param("title", "My super document 1")
.param("tags", tag1Id)
.param("language", "eng")));
Assert.assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus());
// Add an ACL READ for acltag2 with acltag1 on tag1
target().path("/acl").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag1Token)
.put(Entity.form(new Form()
.param("source", tag1Id)
.param("perm", "READ")
.param("target", "acltag2")
.param("type", "USER")), JsonObject.class);
// acltag2 can see tag1
json = target().path("/tag/" + tag1Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token)
.get(JsonObject.class);
Assert.assertFalse(json.getBoolean("writable"));
Assert.assertEquals(3, json.getJsonArray("acls").size());
// acltag2 still cannot edit tag1
response = target().path("/tag/" + tag1Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token)
.post(Entity.form(new Form()
.param("name", "AclTag1")
.param("color", "#ff0000")));
Assert.assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus());
// acltag2 still cannot edit document1
response = target().path("/document/" + document1Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token)
.post(Entity.form(new Form()
.param("title", "My super document 1")
.param("tags", tag1Id)
.param("language", "eng")));
Assert.assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus());
// acltag2 can see document1 with tag1 (non-writable)
json = target().path("/document/" + document1Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token)
.get(JsonObject.class);
tags = json.getJsonArray("tags");
Assert.assertEquals(1, tags.size());
Assert.assertFalse(json.getBoolean("writable"));
Assert.assertEquals(tag1Id, tags.getJsonObject(0).getString("id"));
// acltag2 can see tag1
json = target().path("/tag/list").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token)
.get(JsonObject.class);
tags = json.getJsonArray("tags");
Assert.assertEquals(1, tags.size());
Assert.assertEquals(tag1Id, tags.getJsonObject(0).getString("id"));
// acltag2 can see exactly one document
json = target().path("/document/list")
.queryParam("sort_column", 3)
.queryParam("asc", true)
.request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token)
.get(JsonObject.class);
documents = json.getJsonArray("documents");
Assert.assertEquals(1, documents.size());
// Add an ACL WRITE for acltag2 with acltag1 on tag1
target().path("/acl").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag1Token)
.put(Entity.form(new Form()
.param("source", tag1Id)
.param("perm", "WRITE")
.param("target", "acltag2")
.param("type", "USER")), JsonObject.class);
// acltag2 can see document1 with tag1 (writable)
json = target().path("/document/" + document1Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token)
.get(JsonObject.class);
tags = json.getJsonArray("tags");
Assert.assertEquals(1, tags.size());
Assert.assertTrue(json.getBoolean("writable"));
Assert.assertEquals(tag1Id, tags.getJsonObject(0).getString("id"));
// acltag2 can see and edit tag1
json = target().path("/tag/" + tag1Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token)
.get(JsonObject.class);
Assert.assertTrue(json.getBoolean("writable"));
Assert.assertEquals(4, json.getJsonArray("acls").size());
// acltag2 can edit tag1
target().path("/tag/" + tag1Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token)
.post(Entity.form(new Form()
.param("name", "AclTag1")
.param("color", "#ff0000")), JsonObject.class);
// acltag2 can edit document1
target().path("/document/" + document1Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, acltag2Token)
.post(Entity.form(new Form()
.param("title", "My super document 1")
.param("tags", tag1Id)
.param("language", "eng")), JsonObject.class);
}
}

View File

@ -2,11 +2,17 @@ package com.sismics.docs.rest;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import com.sismics.docs.core.constant.PermType;
import com.sismics.docs.core.dao.jpa.AclDao;
import com.sismics.util.context.ThreadLocalContext;
import com.sismics.util.jpa.EMF;
import org.junit.Assert;
import org.junit.Test;
@ -57,6 +63,35 @@ public class TestAppResource extends BaseJerseyTest {
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
.post(Entity.form(new Form()));
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
// Create a tag
json = target().path("/tag").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
.put(Entity.form(new Form()
.param("name", "Tag4")
.param("color", "#00ff00")), JsonObject.class);
String tagId = json.getString("id");
// Init transactional context
EntityManager em = EMF.get().createEntityManager();
ThreadLocalContext context = ThreadLocalContext.get();
context.setEntityManager(em);
EntityTransaction tx = em.getTransaction();
tx.begin();
// Remove base ACLs
AclDao aclDao = new AclDao();
aclDao.delete(tagId, PermType.READ, "admin", "admin");
aclDao.delete(tagId, PermType.WRITE, "admin", "admin");
Assert.assertEquals(0, aclDao.getBySourceId(tagId).size());
tx.commit();
// Add base ACLs to tags
response = target().path("/app/batch/tag_acls").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
.post(Entity.form(new Form()));
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
Assert.assertEquals(2, aclDao.getBySourceId(tagId).size());
}
/**

View File

@ -87,7 +87,7 @@ public class TestDocumentResource extends BaseJerseyTest {
Assert.assertNotNull(document2Id);
// Add a file
String file1Id = null;
String file1Id;
try (InputStream is = Resources.getResource("file/Einstein-Roosevelt-letter.png").openStream()) {
StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("file", is, "Einstein-Roosevelt-letter.png");
try (FormDataMultiPart multiPart = new FormDataMultiPart()) {
@ -103,7 +103,7 @@ public class TestDocumentResource extends BaseJerseyTest {
}
// Share this document
json = target().path("/share").request()
target().path("/share").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, document1Token)
.put(Entity.form(new Form().param("id", document1Id)), JsonObject.class);
@ -148,7 +148,7 @@ public class TestDocumentResource extends BaseJerseyTest {
Assert.assertNotNull(document3Id);
// Add a file
String file3Id = null;
String file3Id;
try (InputStream is = Resources.getResource("file/Einstein-Roosevelt-letter.png").openStream()) {
StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("file", is, "Einstein-Roosevelt-letter.png");
try (FormDataMultiPart multiPart = new FormDataMultiPart()) {
@ -393,7 +393,7 @@ public class TestDocumentResource extends BaseJerseyTest {
Assert.assertNotNull(document1Id);
// Add a PDF file
String file1Id = null;
String file1Id;
try (InputStream is = Resources.getResource("file/document.odt").openStream()) {
StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("file", is, "document.odt");
try (FormDataMultiPart multiPart = new FormDataMultiPart()) {
@ -452,7 +452,7 @@ public class TestDocumentResource extends BaseJerseyTest {
Assert.assertNotNull(document1Id);
// Add a PDF file
String file1Id = null;
String file1Id;
try (InputStream is = Resources.getResource("file/document.docx").openStream()) {
StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("file", is, "document.docx");
try (FormDataMultiPart multiPart = new FormDataMultiPart()) {
@ -511,7 +511,7 @@ public class TestDocumentResource extends BaseJerseyTest {
Assert.assertNotNull(document1Id);
// Add a PDF file
String file1Id = null;
String file1Id;
try (InputStream is = Resources.getResource("file/wikipedia.pdf").openStream()) {
StreamDataBodyPart streamDataBodyPart = new StreamDataBodyPart("file", is, "wikipedia.pdf");
try (FormDataMultiPart multiPart = new FormDataMultiPart()) {

View File

@ -106,7 +106,7 @@ public class TestGroupResource extends BaseJerseyTest {
Assert.assertEquals(1, users.size());
// Add group1 to g112 (again)
json = target().path("/group/g112").request()
target().path("/group/g112").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
.put(Entity.form(new Form()
.param("username", "group1")), JsonObject.class);
@ -145,7 +145,7 @@ public class TestGroupResource extends BaseJerseyTest {
Assert.assertEquals("group1", members.getString(0));
// Remove group1 from g12new
json = target().path("/group/g12new/group1").request()
target().path("/group/g12new/group1").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
.delete(JsonObject.class);
@ -164,7 +164,7 @@ public class TestGroupResource extends BaseJerseyTest {
Assert.assertTrue(groupList.contains("g112"));
// Delete group g1
json = target().path("/group/g1").request()
target().path("/group/g1").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
.delete(JsonObject.class);

View File

@ -46,6 +46,17 @@ public class TestTagResource extends BaseJerseyTest {
.param("parent", tag3Id)), JsonObject.class);
String tag4Id = json.getString("id");
Assert.assertNotNull(tag4Id);
// Get the tag
json = target().path("/tag/" + tag4Id).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, tag1Token)
.get(JsonObject.class);
Assert.assertEquals("Tag4", json.getString("name"));
Assert.assertEquals("tag1", json.getString("creator"));
Assert.assertEquals("#00ff00", json.getString("color"));
Assert.assertTrue(json.getBoolean("writable"));
JsonArray acls = json.getJsonArray("acls");
Assert.assertEquals(2, acls.size());
// Create a tag with space (not allowed)
Response response = target().path("/tag").request()
@ -55,7 +66,7 @@ public class TestTagResource extends BaseJerseyTest {
Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus()));
// Create a document
json = target().path("/document").request()
target().path("/document").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, tag1Token)
.put(Entity.form(new Form()
.param("title", "My super document 1")
@ -115,15 +126,6 @@ public class TestTagResource extends BaseJerseyTest {
Assert.assertEquals(1, tags.size());
Assert.assertEquals(tag4Id, tags.getJsonObject(0).getString("id"));
// Get tag stats
json = target().path("/tag/stats").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, tag1Token)
.get(JsonObject.class);
JsonArray stats = json.getJsonArray("stats");
Assert.assertTrue(stats.size() == 2);
Assert.assertEquals(1, stats.getJsonObject(0).getInt("count"));
Assert.assertEquals(1, stats.getJsonObject(1).getInt("count"));
// Get all tags
json = target().path("/tag/list").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, tag1Token)
@ -150,7 +152,7 @@ public class TestTagResource extends BaseJerseyTest {
Assert.assertTrue(tags.size() > 0);
Assert.assertEquals("UpdatedName", tags.getJsonObject(1).getString("name"));
Assert.assertEquals("#0000ff", tags.getJsonObject(1).getString("color"));
Assert.assertEquals(JsonValue.NULL, tags.getJsonObject(1).get("parent"));
Assert.assertNull(tags.getJsonObject(1).get("parent"));
// Deletes a tag
target().path("/tag/" + tag4Id).request()

View File

@ -120,9 +120,9 @@
</build>
<modules>
<module>../docs-core</module>
<module>../docs-web-common</module>
<module>../docs-web</module>
<module>docs-core</module>
<module>docs-web-common</module>
<module>docs-web</module>
</modules>
<dependencyManagement>
@ -374,8 +374,9 @@
<artifactId>org.apache.poi.xwpf.converter.pdf</artifactId>
<version>${fr.opensagres.xdocreport.version}</version>
</dependency>
<dependency> <!-- Servlet listener to register SPI ImageIO plugins -->
<!-- Servlet listener to register SPI ImageIO plugins -->
<dependency>
<groupId>com.twelvemonkeys.servlet</groupId>
<artifactId>servlet</artifactId>
<version>${com.twelvemonkeys.imageio.version}</version>
@ -389,7 +390,8 @@
</dependency>
<!-- ImageIO plugins -->
<dependency> <!-- Permissive JPEG plugin -->
<!-- Permissive JPEG plugin -->
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
<version>${com.twelvemonkeys.imageio.version}</version>