mirror of
https://github.com/sismics/docs.git
synced 2025-12-26 08:01:45 +00:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
package com.sismics.docs.rest.resource;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.sismics.docs.core.constant.AclType;
|
||||
import com.sismics.docs.core.constant.ConfigType;
|
||||
@@ -36,10 +34,10 @@ import com.sismics.docs.core.util.DocumentUtil;
|
||||
import com.sismics.docs.core.util.FileUtil;
|
||||
import com.sismics.docs.core.util.MetadataUtil;
|
||||
import com.sismics.docs.core.util.PdfUtil;
|
||||
import com.sismics.docs.core.util.TagUtil;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedList;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedLists;
|
||||
import com.sismics.docs.core.util.jpa.SortCriteria;
|
||||
import com.sismics.docs.rest.util.DocumentSearchCriteriaUtil;
|
||||
import com.sismics.rest.exception.ClientException;
|
||||
import com.sismics.rest.exception.ForbiddenClientException;
|
||||
import com.sismics.rest.exception.ServerException;
|
||||
@@ -57,6 +55,7 @@ import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.DELETE;
|
||||
import jakarta.ws.rs.FormParam;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.HEAD;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.PUT;
|
||||
@@ -69,11 +68,6 @@ import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
|
||||
import org.glassfish.jersey.media.multipart.FormDataParam;
|
||||
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.mail.Message;
|
||||
import javax.mail.MessagingException;
|
||||
@@ -97,26 +91,12 @@ import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Document REST resources.
|
||||
*
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
@Path("/document")
|
||||
public class DocumentResource extends BaseResource {
|
||||
|
||||
protected static final DateTimeParser YEAR_PARSER = DateTimeFormat.forPattern("yyyy").getParser();
|
||||
protected static final DateTimeParser MONTH_PARSER = DateTimeFormat.forPattern("yyyy-MM").getParser();
|
||||
protected static final DateTimeParser DAY_PARSER = DateTimeFormat.forPattern("yyyy-MM-dd").getParser();
|
||||
|
||||
private static final DateTimeFormatter DAY_FORMATTER = new DateTimeFormatter(null, DAY_PARSER);
|
||||
private static final DateTimeFormatter MONTH_FORMATTER = new DateTimeFormatter(null, MONTH_PARSER);
|
||||
private static final DateTimeFormatter YEAR_FORMATTER = new DateTimeFormatter(null, YEAR_PARSER);
|
||||
|
||||
private static final DateTimeParser[] DATE_PARSERS = new DateTimeParser[]{
|
||||
YEAR_PARSER,
|
||||
MONTH_PARSER,
|
||||
DAY_PARSER};
|
||||
private static final DateTimeFormatter DATE_FORMATTER = new DateTimeFormatterBuilder().append( null, DATE_PARSERS).toFormatter();
|
||||
|
||||
/**
|
||||
* Returns a document.
|
||||
*
|
||||
@@ -124,8 +104,8 @@ public class DocumentResource extends BaseResource {
|
||||
* @apiName GetDocument
|
||||
* @apiGroup Document
|
||||
* @apiParam {String} id Document ID
|
||||
* @apiParam {String} share Share ID
|
||||
* @apiParam {Booleans} files If true includes files information
|
||||
* @apiParam {String} [share] Share ID
|
||||
* @apiParam {Boolean} [files] If true includes files information
|
||||
* @apiSuccess {String} id ID
|
||||
* @apiSuccess {String} title Title
|
||||
* @apiSuccess {String} description Description
|
||||
@@ -147,6 +127,7 @@ public class DocumentResource extends BaseResource {
|
||||
* @apiSuccess {String} coverage Coverage
|
||||
* @apiSuccess {String} rights Rights
|
||||
* @apiSuccess {String} creator Username of the creator
|
||||
* @apiSuccess {String} file_id Main file ID
|
||||
* @apiSuccess {Boolean} writable True if the document is writable by the current user
|
||||
* @apiSuccess {Object[]} acls List of ACL
|
||||
* @apiSuccess {String} acls.id ID
|
||||
@@ -198,22 +179,24 @@ public class DocumentResource extends BaseResource {
|
||||
@QueryParam("share") String shareId,
|
||||
@QueryParam("files") Boolean files) {
|
||||
authenticate();
|
||||
|
||||
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
DocumentDto documentDto = documentDao.getDocument(documentId, PermType.READ, getTargetIdList(shareId));
|
||||
if (documentDto == null) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
JsonObjectBuilder document = Json.createObjectBuilder()
|
||||
.add("id", documentDto.getId())
|
||||
.add("title", documentDto.getTitle())
|
||||
.add("description", JsonUtil.nullable(documentDto.getDescription()))
|
||||
.add("create_date", documentDto.getCreateTimestamp())
|
||||
.add("update_date", documentDto.getUpdateTimestamp())
|
||||
.add("language", documentDto.getLanguage())
|
||||
.add("shared", documentDto.getShared())
|
||||
.add("file_count", documentDto.getFileCount());
|
||||
|
||||
JsonObjectBuilder document = createDocumentObjectBuilder(documentDto)
|
||||
.add("creator", documentDto.getCreator())
|
||||
.add("coverage", JsonUtil.nullable(documentDto.getCoverage()))
|
||||
.add("file_count", documentDto.getFileCount())
|
||||
.add("format", JsonUtil.nullable(documentDto.getFormat()))
|
||||
.add("identifier", JsonUtil.nullable(documentDto.getIdentifier()))
|
||||
.add("publisher", JsonUtil.nullable(documentDto.getPublisher()))
|
||||
.add("rights", JsonUtil.nullable(documentDto.getRights()))
|
||||
.add("source", JsonUtil.nullable(documentDto.getSource()))
|
||||
.add("subject", JsonUtil.nullable(documentDto.getSubject()))
|
||||
.add("type", JsonUtil.nullable(documentDto.getType()));
|
||||
|
||||
List<TagDto> tagDtoList = null;
|
||||
if (principal.isAnonymous()) {
|
||||
@@ -227,26 +210,8 @@ public class DocumentResource extends BaseResource {
|
||||
.setTargetIdList(getTargetIdList(null)) // No tags for shares
|
||||
.setDocumentId(documentId),
|
||||
new SortCriteria(1, true));
|
||||
JsonArrayBuilder tags = Json.createArrayBuilder();
|
||||
for (TagDto tagDto : tagDtoList) {
|
||||
tags.add(Json.createObjectBuilder()
|
||||
.add("id", tagDto.getId())
|
||||
.add("name", tagDto.getName())
|
||||
.add("color", tagDto.getColor()));
|
||||
}
|
||||
document.add("tags", tags);
|
||||
document.add("tags", createTagsArrayBuilder(tagDtoList));
|
||||
}
|
||||
|
||||
// Below is specific to GET /document/id
|
||||
document.add("subject", JsonUtil.nullable(documentDto.getSubject()));
|
||||
document.add("identifier", JsonUtil.nullable(documentDto.getIdentifier()));
|
||||
document.add("publisher", JsonUtil.nullable(documentDto.getPublisher()));
|
||||
document.add("format", JsonUtil.nullable(documentDto.getFormat()));
|
||||
document.add("source", JsonUtil.nullable(documentDto.getSource()));
|
||||
document.add("type", JsonUtil.nullable(documentDto.getType()));
|
||||
document.add("coverage", JsonUtil.nullable(documentDto.getCoverage()));
|
||||
document.add("rights", JsonUtil.nullable(documentDto.getRights()));
|
||||
document.add("creator", documentDto.getCreator());
|
||||
|
||||
// Add ACL
|
||||
AclUtil.addAcls(document, documentId, getTargetIdList(shareId));
|
||||
@@ -270,7 +235,7 @@ public class DocumentResource extends BaseResource {
|
||||
}
|
||||
document.add("inherited_acls", aclList);
|
||||
}
|
||||
|
||||
|
||||
// Add contributors
|
||||
ContributorDao contributorDao = new ContributorDao();
|
||||
List<ContributorDto> contributorDtoList = contributorDao.getByDocumentId(documentId);
|
||||
@@ -281,7 +246,7 @@ public class DocumentResource extends BaseResource {
|
||||
.add("email", contributorDto.getEmail()));
|
||||
}
|
||||
document.add("contributors", contributorList);
|
||||
|
||||
|
||||
// Add relations
|
||||
RelationDao relationDao = new RelationDao();
|
||||
List<RelationDto> relationDtoList = relationDao.getByDocumentId(documentId);
|
||||
@@ -320,7 +285,7 @@ public class DocumentResource extends BaseResource {
|
||||
|
||||
return Response.ok().entity(document.build()).build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Export a document to PDF.
|
||||
*
|
||||
@@ -330,7 +295,6 @@ public class DocumentResource extends BaseResource {
|
||||
* @apiParam {String} id Document ID
|
||||
* @apiParam {String} share Share ID
|
||||
* @apiParam {Boolean} metadata If true, export metadata
|
||||
* @apiParam {Boolean} comments If true, export comments
|
||||
* @apiParam {Boolean} fitimagetopage If true, fit the images to pages
|
||||
* @apiParam {Number} margin Margin around the pages, in millimeter
|
||||
* @apiSuccess {String} pdf The whole response is the PDF file
|
||||
@@ -342,7 +306,6 @@ public class DocumentResource extends BaseResource {
|
||||
* @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
|
||||
@@ -353,21 +316,20 @@ public class DocumentResource extends BaseResource {
|
||||
@PathParam("id") String documentId,
|
||||
@QueryParam("share") String shareId,
|
||||
final @QueryParam("metadata") Boolean metadata,
|
||||
final @QueryParam("comments") Boolean comments,
|
||||
final @QueryParam("fitimagetopage") Boolean fitImageToPage,
|
||||
@QueryParam("margin") String marginStr) {
|
||||
authenticate();
|
||||
|
||||
|
||||
// Validate input
|
||||
final int margin = ValidationUtil.validateInteger(marginStr, "margin");
|
||||
|
||||
|
||||
// Get document and check read permission
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
final DocumentDto documentDto = documentDao.getDocument(documentId, PermType.READ, getTargetIdList(shareId));
|
||||
if (documentDto == null) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
|
||||
// Get files
|
||||
FileDao fileDao = new FileDao();
|
||||
UserDao userDao = new UserDao();
|
||||
@@ -378,7 +340,7 @@ public class DocumentResource extends BaseResource {
|
||||
User user = userDao.getById(file.getUserId());
|
||||
file.setPrivateKey(user.getPrivateKey());
|
||||
}
|
||||
|
||||
|
||||
// Convert to PDF
|
||||
StreamingOutput stream = outputStream -> {
|
||||
try {
|
||||
@@ -393,19 +355,36 @@ public class DocumentResource extends BaseResource {
|
||||
.header("Content-Disposition", "inline; filename=\"" + documentDto.getTitle() + ".pdf\"")
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all documents.
|
||||
* Returns all documents, if a parameter is considered invalid, the search result will be empty.
|
||||
*
|
||||
* @api {get} /document/list Get documents
|
||||
* @apiName GetDocumentList
|
||||
* @apiGroup Document
|
||||
* @apiParam {String} limit Total number of documents to return
|
||||
* @apiParam {String} offset Start at this index
|
||||
* @apiParam {Number} sort_column Column index to sort on
|
||||
* @apiParam {Boolean} asc If true, sort in ascending order
|
||||
* @apiParam {String} search Search query (see "Document search syntax" on the top of the page for explanations)
|
||||
* @apiParam {Booleans} files If true includes files information
|
||||
*
|
||||
* @apiParam {String} [limit] Total number of documents to return (default is <code>10</code>)
|
||||
* @apiParam {String} [offset] Start at this index (default is <code>0</code>)
|
||||
* @apiParam {Number} [sort_column] Column index to sort on
|
||||
* @apiParam {Boolean} [asc] If <code>true</code> sorts in ascending order
|
||||
* @apiParam {String} [search] Search query (see "Document search syntax" on the top of the page for explanations) when the input is entered by a human.
|
||||
* @apiParam {Boolean} [files] If <code>true</code> includes files information
|
||||
*
|
||||
* @apiParam {String} [search[after]] The document must have been created after or at the value moment, accepted format is <code>yyyy-MM-dd</code>
|
||||
* @apiParam {String} [search[before]] The document must have been created before or at the value moment, accepted format is <code>yyyy-MM-dd</code>
|
||||
* @apiParam {String} [search[by]] The document must have been created by the specified creator's username with an exact match, the user must not be deleted
|
||||
* @apiParam {String} [search[full]] Used as a search criteria for all fields including the document's files content, several comma-separated values can be specified and the document must match any of them
|
||||
* @apiParam {String} [search[lang]] The document must be of the specified language (example: <code>en</code>)
|
||||
* @apiParam {String} [search[mime]] The document must be of the specified mime type (example: <code>image/png</code>)
|
||||
* @apiParam {String} [search[simple]] Used as a search criteria for all fields except the document's files content, several comma-separated values can be specified and the document must match any of them
|
||||
* @apiParam {Boolean} [search[shared]] If <code>true</code> the document must be shared, else it is ignored
|
||||
* @apiParam {String} [search[tag]] The document must contain a tag or a child of a tag that starts with the value, case is ignored, several comma-separated values can be specified and the document must match all tag filters
|
||||
* @apiParam {String} [search[nottag]] The document must not contain a tag or a child of a tag that starts with the value, case is ignored, several comma-separated values can be specified and the document must match all tag filters
|
||||
* @apiParam {String} [search[title]] The document's title must be the value, several comma-separated values can be specified and the document must match any of the titles
|
||||
* @apiParam {String} [search[uafter]] The document must have been updated after or at the value moment, accepted format is <code>yyyy-MM-dd</code>
|
||||
* @apiParam {String} [search[ubefore]] The document must have been updated before or at the value moment, accepted format is <code>yyyy-MM-dd</code>
|
||||
* @apiParam {String} [search[workflow]] If the value is <code>me</code> the document must have an active route, for other values the criteria is ignored
|
||||
*
|
||||
* @apiSuccess {Number} total Total number of documents
|
||||
* @apiSuccess {Object[]} documents List of documents
|
||||
* @apiSuccess {String} documents.id ID
|
||||
@@ -431,6 +410,7 @@ public class DocumentResource extends BaseResource {
|
||||
* @apiSuccess {String} documents.files.mimetype MIME type
|
||||
* @apiSuccess {String} documents.files.create_date Create date (timestamp)
|
||||
* @apiSuccess {String[]} suggestions List of search suggestions
|
||||
*
|
||||
* @apiError (client) ForbiddenError Access denied
|
||||
* @apiError (server) SearchError Error searching in documents
|
||||
* @apiPermission user
|
||||
@@ -452,19 +432,56 @@ public class DocumentResource extends BaseResource {
|
||||
@QueryParam("sort_column") Integer sortColumn,
|
||||
@QueryParam("asc") Boolean asc,
|
||||
@QueryParam("search") String search,
|
||||
@QueryParam("files") Boolean files) {
|
||||
@QueryParam("files") Boolean files,
|
||||
|
||||
@QueryParam("search[after]") String searchCreatedAfter,
|
||||
@QueryParam("search[before]") String searchCreatedBefore,
|
||||
@QueryParam("search[by]") String searchBy,
|
||||
@QueryParam("search[full]") String searchFull,
|
||||
@QueryParam("search[lang]") String searchLang,
|
||||
@QueryParam("search[mime]") String searchMime,
|
||||
@QueryParam("search[shared]") Boolean searchShared,
|
||||
@QueryParam("search[simple]") String searchSimple,
|
||||
@QueryParam("search[tag]") String searchTag,
|
||||
@QueryParam("search[nottag]") String searchTagNot,
|
||||
@QueryParam("search[title]") String searchTitle,
|
||||
@QueryParam("search[uafter]") String searchUpdatedAfter,
|
||||
@QueryParam("search[ubefore]") String searchUpdatedBefore,
|
||||
@QueryParam("search[searchworkflow]") String searchWorkflow
|
||||
) {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
|
||||
|
||||
JsonObjectBuilder response = Json.createObjectBuilder();
|
||||
JsonArrayBuilder documents = Json.createArrayBuilder();
|
||||
|
||||
|
||||
TagDao tagDao = new TagDao();
|
||||
PaginatedList<DocumentDto> paginatedList = PaginatedLists.create(limit, offset);
|
||||
List<String> suggestionList = Lists.newArrayList();
|
||||
SortCriteria sortCriteria = new SortCriteria(sortColumn, asc);
|
||||
DocumentCriteria documentCriteria = parseSearchQuery(search);
|
||||
|
||||
List<TagDto> allTagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)), null);
|
||||
|
||||
DocumentCriteria documentCriteria = DocumentSearchCriteriaUtil.parseSearchQuery(search, allTagDtoList);
|
||||
DocumentSearchCriteriaUtil.addHttpSearchParams(
|
||||
documentCriteria,
|
||||
searchBy,
|
||||
searchCreatedAfter,
|
||||
searchCreatedBefore,
|
||||
searchFull,
|
||||
searchLang,
|
||||
searchMime,
|
||||
searchShared,
|
||||
searchSimple,
|
||||
searchTag,
|
||||
searchTagNot,
|
||||
searchTitle,
|
||||
searchUpdatedAfter,
|
||||
searchUpdatedBefore,
|
||||
searchWorkflow,
|
||||
allTagDtoList);
|
||||
|
||||
documentCriteria.setTargetIdList(getTargetIdList(null));
|
||||
try {
|
||||
AppContext.getInstance().getIndexingHandler().findByCriteria(paginatedList, suggestionList, documentCriteria, sortCriteria);
|
||||
@@ -488,13 +505,6 @@ public class DocumentResource extends BaseResource {
|
||||
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()
|
||||
.add("id", tagDto.getId())
|
||||
.add("name", tagDto.getName())
|
||||
.add("color", tagDto.getColor()));
|
||||
}
|
||||
|
||||
Long filesCount;
|
||||
Collection<File> filesOfDocument = null;
|
||||
@@ -506,20 +516,13 @@ public class DocumentResource extends BaseResource {
|
||||
filesCount = filesCountByDocument.getOrDefault(documentDto.getId(), 0L);
|
||||
}
|
||||
|
||||
JsonObjectBuilder documentObjectBuilder = Json.createObjectBuilder()
|
||||
.add("id", documentDto.getId())
|
||||
.add("highlight", JsonUtil.nullable(documentDto.getHighlight()))
|
||||
.add("file_id", JsonUtil.nullable(documentDto.getFileId()))
|
||||
.add("title", documentDto.getTitle())
|
||||
.add("description", JsonUtil.nullable(documentDto.getDescription()))
|
||||
.add("create_date", documentDto.getCreateTimestamp())
|
||||
.add("update_date", documentDto.getUpdateTimestamp())
|
||||
.add("language", documentDto.getLanguage())
|
||||
.add("shared", documentDto.getShared())
|
||||
JsonObjectBuilder documentObjectBuilder = createDocumentObjectBuilder(documentDto)
|
||||
.add("active_route", documentDto.isActiveRoute())
|
||||
.add("current_step_name", JsonUtil.nullable(documentDto.getCurrentStepName()))
|
||||
.add("highlight", JsonUtil.nullable(documentDto.getHighlight()))
|
||||
.add("file_count", filesCount)
|
||||
.add("tags", tags);
|
||||
.add("tags", createTagsArrayBuilder(tagDtoList));
|
||||
|
||||
if (Boolean.TRUE == files) {
|
||||
JsonArrayBuilder filesArrayBuilder = Json.createArrayBuilder();
|
||||
for (File fileDb : filesOfDocument) {
|
||||
@@ -538,7 +541,7 @@ public class DocumentResource extends BaseResource {
|
||||
response.add("total", paginatedList.getResultCount())
|
||||
.add("documents", documents)
|
||||
.add("suggestions", suggestions);
|
||||
|
||||
|
||||
return Response.ok().entity(response.build()).build();
|
||||
}
|
||||
|
||||
@@ -567,188 +570,44 @@ public class DocumentResource extends BaseResource {
|
||||
@FormParam("sort_column") Integer sortColumn,
|
||||
@FormParam("asc") Boolean asc,
|
||||
@FormParam("search") String search,
|
||||
@FormParam("files") Boolean files) {
|
||||
return list(limit, offset, sortColumn, asc, search, files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a query according to the specified syntax, eg.:
|
||||
* tag:assurance tag:other before:2012 after:2011-09 shared:yes lang:fra thing
|
||||
*
|
||||
* @param search Search query
|
||||
* @return DocumentCriteria
|
||||
*/
|
||||
private DocumentCriteria parseSearchQuery(String search) {
|
||||
DocumentCriteria documentCriteria = new DocumentCriteria();
|
||||
if (Strings.isNullOrEmpty(search)) {
|
||||
return documentCriteria;
|
||||
}
|
||||
|
||||
TagDao tagDao = new TagDao();
|
||||
List<TagDto> allTagDtoList = tagDao.findByCriteria(new TagCriteria().setTargetIdList(getTargetIdList(null)), null);
|
||||
UserDao userDao = new UserDao();
|
||||
|
||||
String[] criteriaList = search.split(" +");
|
||||
List<String> query = new ArrayList<>();
|
||||
List<String> fullQuery = new ArrayList<>();
|
||||
for (String criteria : criteriaList) {
|
||||
String[] params = criteria.split(":");
|
||||
if (params.length != 2 || Strings.isNullOrEmpty(params[0]) || Strings.isNullOrEmpty(params[1])) {
|
||||
// This is not a special criteria, do a fulltext search on it
|
||||
fullQuery.add(criteria);
|
||||
continue;
|
||||
}
|
||||
String paramName = params[0];
|
||||
String paramValue = params[1];
|
||||
|
||||
switch (paramName) {
|
||||
case "tag":
|
||||
case "!tag":
|
||||
// New tag criteria
|
||||
List<TagDto> tagDtoList = TagUtil.findByName(paramValue, allTagDtoList);
|
||||
if (tagDtoList.isEmpty()) {
|
||||
// No tag found, the request must return nothing
|
||||
documentCriteria.getTagIdList().add(Lists.newArrayList(UUID.randomUUID().toString()));
|
||||
} else {
|
||||
List<String> tagIdList = Lists.newArrayList();
|
||||
for (TagDto tagDto : tagDtoList) {
|
||||
tagIdList.add(tagDto.getId());
|
||||
List<TagDto> childrenTagDtoList = TagUtil.findChildren(tagDto, allTagDtoList);
|
||||
for (TagDto childrenTagDto : childrenTagDtoList) {
|
||||
tagIdList.add(childrenTagDto.getId());
|
||||
}
|
||||
}
|
||||
if (paramName.startsWith("!")) {
|
||||
documentCriteria.getExcludedTagIdList().add(tagIdList);
|
||||
} else {
|
||||
documentCriteria.getTagIdList().add(tagIdList);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "after":
|
||||
case "before":
|
||||
case "uafter":
|
||||
case "ubefore":
|
||||
// New date span criteria
|
||||
try {
|
||||
boolean isUpdated = paramName.startsWith("u");
|
||||
DateTime date = DATE_FORMATTER.parseDateTime(paramValue);
|
||||
if (paramName.endsWith("before")) {
|
||||
if (isUpdated) documentCriteria.setUpdateDateMax(date.toDate());
|
||||
else documentCriteria.setCreateDateMax(date.toDate());
|
||||
} else {
|
||||
if (isUpdated) documentCriteria.setUpdateDateMin(date.toDate());
|
||||
else documentCriteria.setCreateDateMin(date.toDate());
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Invalid date, returns no documents
|
||||
documentCriteria.setCreateDateMin(new Date(0));
|
||||
documentCriteria.setCreateDateMax(new Date(0));
|
||||
}
|
||||
break;
|
||||
case "uat":
|
||||
case "at":
|
||||
// New specific date criteria
|
||||
boolean isUpdated = params[0].startsWith("u");
|
||||
try {
|
||||
switch (paramValue.length()) {
|
||||
case 10: {
|
||||
DateTime date = DATE_FORMATTER.parseDateTime(params[1]);
|
||||
if (isUpdated) {
|
||||
documentCriteria.setUpdateDateMin(date.toDate());
|
||||
documentCriteria.setUpdateDateMax(date.plusDays(1).minusSeconds(1).toDate());
|
||||
} else {
|
||||
documentCriteria.setCreateDateMin(date.toDate());
|
||||
documentCriteria.setCreateDateMax(date.plusDays(1).minusSeconds(1).toDate());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 7: {
|
||||
DateTime date = MONTH_FORMATTER.parseDateTime(params[1]);
|
||||
if (isUpdated) {
|
||||
documentCriteria.setUpdateDateMin(date.toDate());
|
||||
documentCriteria.setUpdateDateMax(date.plusMonths(1).minusSeconds(1).toDate());
|
||||
} else {
|
||||
documentCriteria.setCreateDateMin(date.toDate());
|
||||
documentCriteria.setCreateDateMax(date.plusMonths(1).minusSeconds(1).toDate());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
DateTime date = YEAR_FORMATTER.parseDateTime(params[1]);
|
||||
if (isUpdated) {
|
||||
documentCriteria.setUpdateDateMin(date.toDate());
|
||||
documentCriteria.setUpdateDateMax(date.plusYears(1).minusSeconds(1).toDate());
|
||||
} else {
|
||||
documentCriteria.setCreateDateMin(date.toDate());
|
||||
documentCriteria.setCreateDateMax(date.plusYears(1).minusSeconds(1).toDate());
|
||||
}
|
||||
break;
|
||||
} default: {
|
||||
// Invalid format, returns no documents
|
||||
documentCriteria.setCreateDateMin(new Date(0));
|
||||
documentCriteria.setCreateDateMax(new Date(0));
|
||||
}
|
||||
}
|
||||
} 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
|
||||
documentCriteria.setShared(paramValue.equals("yes"));
|
||||
break;
|
||||
case "lang":
|
||||
// New language criteria
|
||||
if (Constants.SUPPORTED_LANGUAGES.contains(paramValue)) {
|
||||
documentCriteria.setLanguage(paramValue);
|
||||
} else {
|
||||
// Unsupported language, returns no documents
|
||||
documentCriteria.setLanguage(UUID.randomUUID().toString());
|
||||
}
|
||||
break;
|
||||
case "mime":
|
||||
// New mime type criteria
|
||||
documentCriteria.setMimeType(paramValue);
|
||||
break;
|
||||
case "by":
|
||||
// New creator criteria
|
||||
User user = userDao.getActiveByUsername(paramValue);
|
||||
if (user == null) {
|
||||
// This user doesn't exist, return nothing
|
||||
documentCriteria.setCreatorId(UUID.randomUUID().toString());
|
||||
} else {
|
||||
// This user exists, search its documents
|
||||
documentCriteria.setCreatorId(user.getId());
|
||||
}
|
||||
break;
|
||||
case "workflow":
|
||||
// New shared state criteria
|
||||
documentCriteria.setActiveRoute(paramValue.equals("me"));
|
||||
break;
|
||||
case "simple":
|
||||
// New simple search criteria
|
||||
query.add(paramValue);
|
||||
break;
|
||||
case "full":
|
||||
// New fulltext search criteria
|
||||
fullQuery.add(paramValue);
|
||||
break;
|
||||
case "title":
|
||||
// New title criteria
|
||||
documentCriteria.getTitleList().add(paramValue);
|
||||
break;
|
||||
default:
|
||||
fullQuery.add(criteria);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
documentCriteria.setSearch(Joiner.on(" ").join(query));
|
||||
documentCriteria.setFullSearch(Joiner.on(" ").join(fullQuery));
|
||||
return documentCriteria;
|
||||
@FormParam("files") Boolean files,
|
||||
@FormParam("search[after]") String searchCreatedAfter,
|
||||
@FormParam("search[before]") String searchCreatedBefore,
|
||||
@FormParam("search[by]") String searchBy,
|
||||
@FormParam("search[full]") String searchFull,
|
||||
@FormParam("search[lang]") String searchLang,
|
||||
@FormParam("search[mime]") String searchMime,
|
||||
@FormParam("search[shared]") Boolean searchShared,
|
||||
@FormParam("search[simple]") String searchSimple,
|
||||
@FormParam("search[tag]") String searchTag,
|
||||
@FormParam("search[nottag]") String searchTagNot,
|
||||
@FormParam("search[title]") String searchTitle,
|
||||
@FormParam("search[uafter]") String searchUpdatedAfter,
|
||||
@FormParam("search[ubefore]") String searchUpdatedBefore,
|
||||
@FormParam("search[searchworkflow]") String searchWorkflow
|
||||
) {
|
||||
return list(
|
||||
limit,
|
||||
offset,
|
||||
sortColumn,
|
||||
asc,
|
||||
search,
|
||||
files,
|
||||
searchCreatedAfter,
|
||||
searchCreatedBefore,
|
||||
searchBy,
|
||||
searchFull,
|
||||
searchLang,
|
||||
searchMime,
|
||||
searchShared,
|
||||
searchSimple,
|
||||
searchTag,
|
||||
searchTagNot,
|
||||
searchTitle,
|
||||
searchUpdatedAfter,
|
||||
searchUpdatedBefore,
|
||||
searchWorkflow
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -818,7 +677,7 @@ public class DocumentResource extends BaseResource {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
|
||||
|
||||
// Validate input data
|
||||
title = ValidationUtil.validateLength(title, "title", 1, 100, false);
|
||||
language = ValidationUtil.validateLength(language, "language", 3, 7, false);
|
||||
@@ -882,7 +741,7 @@ public class DocumentResource extends BaseResource {
|
||||
.add("id", document.getId());
|
||||
return Response.ok().entity(response.build()).build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the document.
|
||||
*
|
||||
@@ -904,7 +763,7 @@ public class DocumentResource extends BaseResource {
|
||||
* @apiParam {String[]} [relations] List of related documents ID
|
||||
* @apiParam {String[]} [metadata_id] List of metadata ID
|
||||
* @apiParam {String[]} [metadata_value] List of metadata values
|
||||
* @apiParam {String} language Language
|
||||
* @apiParam {String} [language] Language
|
||||
* @apiParam {Number} [create_date] Create date (timestamp)
|
||||
* @apiSuccess {String} id Document ID
|
||||
* @apiError (client) ForbiddenError Access denied or document not writable
|
||||
@@ -940,7 +799,7 @@ public class DocumentResource extends BaseResource {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
|
||||
|
||||
// Validate input data
|
||||
title = ValidationUtil.validateLength(title, "title", 1, 100, false);
|
||||
language = ValidationUtil.validateLength(language, "language", 3, 7, false);
|
||||
@@ -957,20 +816,20 @@ public class DocumentResource extends BaseResource {
|
||||
if (language != null && !Constants.SUPPORTED_LANGUAGES.contains(language)) {
|
||||
throw new ClientException("ValidationError", MessageFormat.format("{0} is not a supported language", language));
|
||||
}
|
||||
|
||||
|
||||
// Check write permission
|
||||
AclDao aclDao = new AclDao();
|
||||
if (!aclDao.checkPermission(id, PermType.WRITE, getTargetIdList(null))) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
|
||||
|
||||
// Get the document
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
Document document = documentDao.getById(id);
|
||||
if (document == null) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
|
||||
// Update the document
|
||||
document.setTitle(title);
|
||||
document.setDescription(description);
|
||||
@@ -988,12 +847,12 @@ public class DocumentResource extends BaseResource {
|
||||
} else {
|
||||
document.setCreateDate(createDate);
|
||||
}
|
||||
|
||||
|
||||
documentDao.update(document, principal.getId());
|
||||
|
||||
|
||||
// Update tags
|
||||
updateTagList(id, tagList);
|
||||
|
||||
|
||||
// Update relations
|
||||
updateRelationList(id, relationList);
|
||||
|
||||
@@ -1009,7 +868,7 @@ public class DocumentResource extends BaseResource {
|
||||
documentUpdatedAsyncEvent.setUserId(principal.getId());
|
||||
documentUpdatedAsyncEvent.setDocumentId(id);
|
||||
ThreadLocalContext.get().addAsyncEvent(documentUpdatedAsyncEvent);
|
||||
|
||||
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
.add("id", id);
|
||||
return Response.ok().entity(response.build()).build();
|
||||
@@ -1144,7 +1003,7 @@ public class DocumentResource extends BaseResource {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
List<File> fileList = fileDao.getByDocumentId(principal.getId(), id);
|
||||
|
||||
|
||||
// Delete the document
|
||||
documentDao.delete(id, principal.getId());
|
||||
|
||||
@@ -1162,7 +1021,7 @@ public class DocumentResource extends BaseResource {
|
||||
documentDeletedAsyncEvent.setUserId(principal.getId());
|
||||
documentDeletedAsyncEvent.setDocumentId(id);
|
||||
ThreadLocalContext.get().addAsyncEvent(documentDeletedAsyncEvent);
|
||||
|
||||
|
||||
// Always return OK
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
.add("status", "ok");
|
||||
@@ -1215,4 +1074,27 @@ public class DocumentResource extends BaseResource {
|
||||
relationDao.updateRelationList(documentId, documentIdSet);
|
||||
}
|
||||
}
|
||||
|
||||
private JsonObjectBuilder createDocumentObjectBuilder(DocumentDto documentDto) {
|
||||
return Json.createObjectBuilder()
|
||||
.add("create_date", documentDto.getCreateTimestamp())
|
||||
.add("description", JsonUtil.nullable(documentDto.getDescription()))
|
||||
.add("file_id", JsonUtil.nullable(documentDto.getFileId()))
|
||||
.add("id", documentDto.getId())
|
||||
.add("language", documentDto.getLanguage())
|
||||
.add("shared", documentDto.getShared())
|
||||
.add("title", documentDto.getTitle())
|
||||
.add("update_date", documentDto.getUpdateTimestamp());
|
||||
}
|
||||
|
||||
private static JsonArrayBuilder createTagsArrayBuilder(List<TagDto> tagDtoList) {
|
||||
JsonArrayBuilder tags = Json.createArrayBuilder();
|
||||
for (TagDto tagDto : tagDtoList) {
|
||||
tags.add(Json.createObjectBuilder()
|
||||
.add("id", tagDto.getId())
|
||||
.add("name", tagDto.getName())
|
||||
.add("color", tagDto.getColor()));
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,8 +67,8 @@ public class FileResource extends BaseResource {
|
||||
* This resource accepts only multipart/form-data.
|
||||
* @apiName PutFile
|
||||
* @apiGroup File
|
||||
* @apiParam {String} id Document ID
|
||||
* @apiParam {String} previousFileId ID of the file to replace by this new version
|
||||
* @apiParam {String} [id] Document ID
|
||||
* @apiParam {String} [previousFileId] ID of the file to replace by this new version
|
||||
* @apiParam {String} file File data
|
||||
* @apiSuccess {String} status Status OK
|
||||
* @apiSuccess {String} id File ID
|
||||
@@ -390,8 +390,8 @@ public class FileResource extends BaseResource {
|
||||
* @api {get} /file/list Get files
|
||||
* @apiName GetFileList
|
||||
* @apiGroup File
|
||||
* @apiParam {String} id Document ID
|
||||
* @apiParam {String} share Share ID
|
||||
* @apiParam {String} [id] Document ID
|
||||
* @apiParam {String} [share] Share ID
|
||||
* @apiSuccess {Object[]} files List of files
|
||||
* @apiSuccess {String} files.id ID
|
||||
* @apiSuccess {String} files.processing True if the file is currently processing
|
||||
@@ -497,7 +497,6 @@ public class FileResource extends BaseResource {
|
||||
* @apiName DeleteFile
|
||||
* @apiGroup File
|
||||
* @apiParam {String} id File ID
|
||||
* @apiParam {String} share Share ID
|
||||
* @apiSuccess {String} status Status OK
|
||||
* @apiError (client) ForbiddenError Access denied
|
||||
* @apiError (client) NotFound File or document not found
|
||||
|
||||
@@ -313,7 +313,7 @@ public class GroupResource extends BaseResource {
|
||||
* @return Response
|
||||
*/
|
||||
@DELETE
|
||||
@Path("{groupName: [a-zA-Z0-9_]+}/{username: [a-zA-Z0-9_@\\.]+}")
|
||||
@Path("{groupName: [a-zA-Z0-9_]+}/{username: [a-zA-Z0-9_@.-]+}")
|
||||
public Response removeMember(@PathParam("groupName") String groupName,
|
||||
@PathParam("username") String username) {
|
||||
if (!authenticate()) {
|
||||
|
||||
@@ -195,7 +195,7 @@ public class UserResource extends BaseResource {
|
||||
* @return Response
|
||||
*/
|
||||
@POST
|
||||
@Path("{username: [a-zA-Z0-9_@\\.]+}")
|
||||
@Path("{username: [a-zA-Z0-9_@.-]+}")
|
||||
public Response update(
|
||||
@PathParam("username") String username,
|
||||
@FormParam("password") String password,
|
||||
@@ -497,7 +497,7 @@ public class UserResource extends BaseResource {
|
||||
* @return Response
|
||||
*/
|
||||
@DELETE
|
||||
@Path("{username: [a-zA-Z0-9_@\\.]+}")
|
||||
@Path("{username: [a-zA-Z0-9_@.-]+}")
|
||||
public Response delete(@PathParam("username") String username) {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
@@ -563,7 +563,7 @@ public class UserResource extends BaseResource {
|
||||
* @return Response
|
||||
*/
|
||||
@POST
|
||||
@Path("{username: [a-zA-Z0-9_@\\.]+}/disable_totp")
|
||||
@Path("{username: [a-zA-Z0-9_@.-]+}/disable_totp")
|
||||
public Response disableTotpUsername(@PathParam("username") String username) {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
@@ -685,7 +685,7 @@ public class UserResource extends BaseResource {
|
||||
* @return Response
|
||||
*/
|
||||
@GET
|
||||
@Path("{username: [a-zA-Z0-9_@\\.]+}")
|
||||
@Path("{username: [a-zA-Z0-9_@.-]+}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response view(@PathParam("username") String username) {
|
||||
if (!authenticate()) {
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
package com.sismics.docs.rest.util;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.sismics.docs.core.constant.Constants;
|
||||
import com.sismics.docs.core.dao.UserDao;
|
||||
import com.sismics.docs.core.dao.criteria.DocumentCriteria;
|
||||
import com.sismics.docs.core.dao.dto.TagDto;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class DocumentSearchCriteriaUtil {
|
||||
private static final DateTimeParser YEAR_PARSER = DateTimeFormat.forPattern("yyyy").getParser();
|
||||
private static final DateTimeParser MONTH_PARSER = DateTimeFormat.forPattern("yyyy-MM").getParser();
|
||||
private static final DateTimeParser DAY_PARSER = DateTimeFormat.forPattern("yyyy-MM-dd").getParser();
|
||||
private static final DateTimeParser[] DATE_PARSERS = new DateTimeParser[]{
|
||||
YEAR_PARSER,
|
||||
MONTH_PARSER,
|
||||
DAY_PARSER};
|
||||
|
||||
private static final DateTimeFormatter YEAR_FORMATTER = new DateTimeFormatter(null, YEAR_PARSER);
|
||||
private static final DateTimeFormatter MONTH_FORMATTER = new DateTimeFormatter(null, MONTH_PARSER);
|
||||
private static final DateTimeFormatter DAY_FORMATTER = new DateTimeFormatter(null, DAY_PARSER);
|
||||
private static final DateTimeFormatter DATES_FORMATTER = new DateTimeFormatterBuilder().append(null, DATE_PARSERS).toFormatter();
|
||||
|
||||
private static final String PARAMETER_WITH_MULTIPLE_VALUES_SEPARATOR = ",";
|
||||
private static final String WORKFLOW_ME = "me";
|
||||
|
||||
/**
|
||||
* Parse a query according to the specified syntax, eg.:
|
||||
* tag:assurance tag:other before:2012 after:2011-09 shared:yes lang:fra thing
|
||||
*
|
||||
* @param search Search query
|
||||
* @param allTagDtoList List of tags
|
||||
* @return DocumentCriteria
|
||||
*/
|
||||
public static DocumentCriteria parseSearchQuery(String search, List<TagDto> allTagDtoList) {
|
||||
DocumentCriteria documentCriteria = new DocumentCriteria();
|
||||
if (Strings.isNullOrEmpty(search)) {
|
||||
return documentCriteria;
|
||||
}
|
||||
|
||||
String[] criteriaList = search.split(" +");
|
||||
List<String> simpleQuery = new ArrayList<>();
|
||||
List<String> fullQuery = new ArrayList<>();
|
||||
for (String criteria : criteriaList) {
|
||||
String[] params = criteria.split(":");
|
||||
if (params.length != 2 || Strings.isNullOrEmpty(params[0]) || Strings.isNullOrEmpty(params[1])) {
|
||||
// This is not a special criteria, do a fulltext search on it
|
||||
fullQuery.add(criteria);
|
||||
continue;
|
||||
}
|
||||
String paramName = params[0];
|
||||
String paramValue = params[1];
|
||||
|
||||
switch (paramName) {
|
||||
case "tag":
|
||||
case "!tag":
|
||||
parseTagCriteria(documentCriteria, paramValue, allTagDtoList, paramName.startsWith("!"));
|
||||
break;
|
||||
case "after":
|
||||
case "before":
|
||||
case "uafter":
|
||||
case "ubefore":
|
||||
parseDateCriteria(documentCriteria, paramValue, DATES_FORMATTER, paramName.startsWith("u"), paramName.endsWith("before"));
|
||||
break;
|
||||
case "uat":
|
||||
case "at":
|
||||
parseDateAtCriteria(documentCriteria, paramValue, params[0].startsWith("u"));
|
||||
break;
|
||||
case "shared":
|
||||
documentCriteria.setShared(paramValue.equals("yes"));
|
||||
break;
|
||||
case "lang":
|
||||
parseLangCriteria(documentCriteria, paramValue);
|
||||
break;
|
||||
case "mime":
|
||||
documentCriteria.setMimeType(paramValue);
|
||||
break;
|
||||
case "by":
|
||||
parseByCriteria(documentCriteria, paramValue);
|
||||
break;
|
||||
case "workflow":
|
||||
documentCriteria.setActiveRoute(paramValue.equals(WORKFLOW_ME));
|
||||
break;
|
||||
case "simple":
|
||||
simpleQuery.add(paramValue);
|
||||
break;
|
||||
case "full":
|
||||
fullQuery.add(paramValue);
|
||||
break;
|
||||
case "title":
|
||||
documentCriteria.getTitleList().add(paramValue);
|
||||
break;
|
||||
default:
|
||||
fullQuery.add(criteria);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
documentCriteria.setSimpleSearch(Joiner.on(" ").join(simpleQuery));
|
||||
documentCriteria.setFullSearch(Joiner.on(" ").join(fullQuery));
|
||||
return documentCriteria;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fill the document criteria with various possible parameters
|
||||
*
|
||||
* @param documentCriteria structure to be filled
|
||||
* @param searchBy author
|
||||
* @param searchCreatedAfter creation moment after
|
||||
* @param searchCreatedBefore creation moment before
|
||||
* @param searchFull full search
|
||||
* @param searchLang lang
|
||||
* @param searchMime mime type
|
||||
* @param searchShared share state
|
||||
* @param searchSimple search in
|
||||
* @param searchTag tags or parent tags
|
||||
* @param searchNotTag tags or parent tags to ignore
|
||||
* @param searchTitle title
|
||||
* @param searchUpdatedAfter update moment after
|
||||
* @param searchUpdatedBefore update moment before
|
||||
* @param searchWorkflow exiting workflow
|
||||
* @param allTagDtoList list of existing tags
|
||||
*/
|
||||
public static void addHttpSearchParams(
|
||||
DocumentCriteria documentCriteria,
|
||||
String searchBy,
|
||||
String searchCreatedAfter,
|
||||
String searchCreatedBefore,
|
||||
String searchFull,
|
||||
String searchLang,
|
||||
String searchMime,
|
||||
Boolean searchShared,
|
||||
String searchSimple,
|
||||
String searchTag,
|
||||
String searchNotTag,
|
||||
String searchTitle,
|
||||
String searchUpdatedAfter,
|
||||
String searchUpdatedBefore,
|
||||
String searchWorkflow,
|
||||
List<TagDto> allTagDtoList
|
||||
) {
|
||||
if (searchBy != null) {
|
||||
parseByCriteria(documentCriteria, searchBy);
|
||||
}
|
||||
if (searchCreatedAfter != null) {
|
||||
parseDateCriteria(documentCriteria, searchCreatedAfter, DAY_FORMATTER, false, false);
|
||||
}
|
||||
if (searchCreatedBefore != null) {
|
||||
parseDateCriteria(documentCriteria, searchCreatedBefore, DAY_FORMATTER, false, true);
|
||||
}
|
||||
if (searchFull != null) {
|
||||
documentCriteria.setFullSearch(Joiner.on(" ").join(searchFull.split(PARAMETER_WITH_MULTIPLE_VALUES_SEPARATOR)));
|
||||
}
|
||||
if (searchLang != null) {
|
||||
parseLangCriteria(documentCriteria, searchLang);
|
||||
}
|
||||
if (searchMime != null) {
|
||||
documentCriteria.setMimeType(searchMime);
|
||||
}
|
||||
if ((searchShared != null) && searchShared) {
|
||||
documentCriteria.setShared(true);
|
||||
}
|
||||
if (searchSimple != null) {
|
||||
documentCriteria.setSimpleSearch(Joiner.on(" ").join(searchSimple.split(PARAMETER_WITH_MULTIPLE_VALUES_SEPARATOR)));
|
||||
}
|
||||
if (searchTitle != null) {
|
||||
documentCriteria.getTitleList().addAll(Arrays.asList(searchTitle.split(PARAMETER_WITH_MULTIPLE_VALUES_SEPARATOR)));
|
||||
}
|
||||
if (searchTag != null) {
|
||||
for (String tag : searchTag.split(PARAMETER_WITH_MULTIPLE_VALUES_SEPARATOR)) {
|
||||
parseTagCriteria(documentCriteria, tag, allTagDtoList, false);
|
||||
}
|
||||
}
|
||||
if (searchNotTag != null) {
|
||||
for (String tag : searchNotTag.split(PARAMETER_WITH_MULTIPLE_VALUES_SEPARATOR)) {
|
||||
parseTagCriteria(documentCriteria, tag, allTagDtoList, true);
|
||||
}
|
||||
}
|
||||
if (searchUpdatedAfter != null) {
|
||||
parseDateCriteria(documentCriteria, searchUpdatedAfter, DAY_FORMATTER, true, false);
|
||||
}
|
||||
if (searchUpdatedBefore != null) {
|
||||
parseDateCriteria(documentCriteria, searchUpdatedBefore, DAY_FORMATTER, true, true);
|
||||
}
|
||||
if ((WORKFLOW_ME.equals(searchWorkflow))) {
|
||||
documentCriteria.setActiveRoute(true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseDateCriteria(DocumentCriteria documentCriteria, String value, DateTimeFormatter formatter, boolean isUpdated, boolean isBefore) {
|
||||
try {
|
||||
DateTime date = formatter.parseDateTime(value);
|
||||
if (isBefore) {
|
||||
if (isUpdated) {
|
||||
documentCriteria.setUpdateDateMax(date.toDate());
|
||||
} else {
|
||||
documentCriteria.setCreateDateMax(date.toDate());
|
||||
}
|
||||
} else {
|
||||
if (isUpdated) {
|
||||
documentCriteria.setUpdateDateMin(date.toDate());
|
||||
} else {
|
||||
documentCriteria.setCreateDateMin(date.toDate());
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Invalid date, returns no documents
|
||||
documentCriteria.setCreateDateMin(new Date(0));
|
||||
documentCriteria.setCreateDateMax(new Date(0));
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseDateAtCriteria(DocumentCriteria documentCriteria, String value, boolean isUpdated) {
|
||||
try {
|
||||
switch (value.length()) {
|
||||
case 10: {
|
||||
DateTime date = DATES_FORMATTER.parseDateTime(value);
|
||||
if (isUpdated) {
|
||||
documentCriteria.setUpdateDateMin(date.toDate());
|
||||
documentCriteria.setUpdateDateMax(date.plusDays(1).minusSeconds(1).toDate());
|
||||
} else {
|
||||
documentCriteria.setCreateDateMin(date.toDate());
|
||||
documentCriteria.setCreateDateMax(date.plusDays(1).minusSeconds(1).toDate());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 7: {
|
||||
DateTime date = MONTH_FORMATTER.parseDateTime(value);
|
||||
if (isUpdated) {
|
||||
documentCriteria.setUpdateDateMin(date.toDate());
|
||||
documentCriteria.setUpdateDateMax(date.plusMonths(1).minusSeconds(1).toDate());
|
||||
} else {
|
||||
documentCriteria.setCreateDateMin(date.toDate());
|
||||
documentCriteria.setCreateDateMax(date.plusMonths(1).minusSeconds(1).toDate());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
DateTime date = YEAR_FORMATTER.parseDateTime(value);
|
||||
if (isUpdated) {
|
||||
documentCriteria.setUpdateDateMin(date.toDate());
|
||||
documentCriteria.setUpdateDateMax(date.plusYears(1).minusSeconds(1).toDate());
|
||||
} else {
|
||||
documentCriteria.setCreateDateMin(date.toDate());
|
||||
documentCriteria.setCreateDateMax(date.plusYears(1).minusSeconds(1).toDate());
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Invalid format, returns no documents
|
||||
documentCriteria.setCreateDateMin(new Date(0));
|
||||
documentCriteria.setCreateDateMax(new Date(0));
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Invalid date, returns no documents
|
||||
documentCriteria.setCreateDateMin(new Date(0));
|
||||
documentCriteria.setCreateDateMax(new Date(0));
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseTagCriteria(DocumentCriteria documentCriteria, String value, List<TagDto> allTagDtoList, boolean exclusion) {
|
||||
List<TagDto> tagDtoList = TagUtil.findByName(value, allTagDtoList);
|
||||
if (tagDtoList.isEmpty()) {
|
||||
// No tag found, the request must return nothing
|
||||
documentCriteria.getTagIdList().add(Lists.newArrayList(UUID.randomUUID().toString()));
|
||||
} else {
|
||||
List<String> tagIdList = Lists.newArrayList();
|
||||
for (TagDto tagDto : tagDtoList) {
|
||||
tagIdList.add(tagDto.getId());
|
||||
List<TagDto> childrenTagDtoList = TagUtil.findChildren(tagDto, allTagDtoList);
|
||||
for (TagDto childrenTagDto : childrenTagDtoList) {
|
||||
tagIdList.add(childrenTagDto.getId());
|
||||
}
|
||||
}
|
||||
if (exclusion) {
|
||||
documentCriteria.getExcludedTagIdList().add(tagIdList);
|
||||
} else {
|
||||
documentCriteria.getTagIdList().add(tagIdList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseLangCriteria(DocumentCriteria documentCriteria, String value) {
|
||||
// New language criteria
|
||||
if (Constants.SUPPORTED_LANGUAGES.contains(value)) {
|
||||
documentCriteria.setLanguage(value);
|
||||
} else {
|
||||
// Unsupported language, returns no documents
|
||||
documentCriteria.setLanguage(UUID.randomUUID().toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseByCriteria(DocumentCriteria documentCriteria, String value) {
|
||||
User user = new UserDao().getActiveByUsername(value);
|
||||
if (user == null) {
|
||||
// This user doesn't exist, return nothing
|
||||
documentCriteria.setCreatorId(UUID.randomUUID().toString());
|
||||
} else {
|
||||
// This user exists, search its documents
|
||||
documentCriteria.setCreatorId(user.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.sismics.docs.rest.util;
|
||||
|
||||
import com.sismics.docs.core.dao.dto.TagDto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Tag utilities.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class TagUtil {
|
||||
/**
|
||||
* Recursively find children of a tag.
|
||||
*
|
||||
* @param parentTagDto Parent tag
|
||||
* @param allTagDtoList List of all tags
|
||||
* @return Children tags
|
||||
*/
|
||||
public static List<TagDto> findChildren(TagDto parentTagDto, List<TagDto> allTagDtoList) {
|
||||
List<TagDto> childrenTagDtoList = new ArrayList<>();
|
||||
|
||||
for (TagDto tagDto : allTagDtoList) {
|
||||
if (parentTagDto.getId().equals(tagDto.getParentId())) {
|
||||
childrenTagDtoList.add(tagDto);
|
||||
childrenTagDtoList.addAll(findChildren(tagDto, allTagDtoList));
|
||||
}
|
||||
}
|
||||
|
||||
return childrenTagDtoList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find tags by name (start with, ignore case).
|
||||
*
|
||||
* @param name Name
|
||||
* @param allTagDtoList List of all tags
|
||||
* @return List of filtered tags
|
||||
*/
|
||||
public static List<TagDto> findByName(String name, List<TagDto> allTagDtoList) {
|
||||
if (name.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<TagDto> tagDtoList = new ArrayList<>();
|
||||
name = name.toLowerCase();
|
||||
for (TagDto tagDto : allTagDtoList) {
|
||||
if (tagDto.getName().toLowerCase().startsWith(name)) {
|
||||
tagDtoList.add(tagDto);
|
||||
}
|
||||
}
|
||||
return tagDtoList;
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,16 @@
|
||||
<async-supported>true</async-supported>
|
||||
</filter>
|
||||
|
||||
<filter>
|
||||
<filter-name>jwtBasedSecurityFilter</filter-name>
|
||||
<filter-class>com.sismics.util.filter.JwtBasedSecurityFilter</filter-class>
|
||||
<async-supported>true</async-supported>
|
||||
<init-param>
|
||||
<param-name>enabled</param-name>
|
||||
<param-value>false</param-value>
|
||||
</init-param>
|
||||
</filter>
|
||||
|
||||
<filter>
|
||||
<filter-name>headerBasedSecurityFilter</filter-name>
|
||||
<filter-class>com.sismics.util.filter.HeaderBasedSecurityFilter</filter-class>
|
||||
@@ -59,6 +69,11 @@
|
||||
<url-pattern>/api/*</url-pattern>
|
||||
</filter-mapping>
|
||||
|
||||
<filter-mapping>
|
||||
<filter-name>jwtBasedSecurityFilter</filter-name>
|
||||
<url-pattern>/api/*</url-pattern>
|
||||
</filter-mapping>
|
||||
|
||||
<filter-mapping>
|
||||
<filter-name>headerBasedSecurityFilter</filter-name>
|
||||
<url-pattern>/api/*</url-pattern>
|
||||
|
||||
@@ -50,11 +50,11 @@ curl -i -X POST -H "Cookie: auth_token=64085630-2ae6-415c-9a92-4b22c107eaa4" htt
|
||||
|
||||
## Document search syntax
|
||||
|
||||
The `/api/document/list` endpoint use a String `search` parameter.
|
||||
The `/api/document/list` endpoint use a String `search` parameter, useful when the query is entered by a human.
|
||||
|
||||
This parameter is split in segments using the space character (the other whitespace characters are not considered).
|
||||
|
||||
If a segment contains exactly one colon (`:`), it will used as a field criteria (see bellow).
|
||||
If a segment contains exactly one colon (`:`), it will be used as a field criteria (see bellow).
|
||||
In other cases (zero or more than one colon), the segment will be used as a search criteria for all fields including the document's files content.
|
||||
|
||||
### Search fields
|
||||
@@ -69,7 +69,7 @@ If a search `VALUE` is considered invalid, the search result will be empty.
|
||||
* `at:VALUE`: the document must have been created at the `VALUE` moment, accepted format are `yyyy`, `yyyy-MM` and `yyyy-MM-dd` (for `yyyy` it must be the same year, for `yyyy-MM` the same month, for `yyyy-MM-dd` the same day)
|
||||
* `before:VALUE`: the document must have been created before or at the `VALUE` moment, accepted format are `yyyy`, `yyyy-MM` and `yyyy-MM-dd`
|
||||
* `uafter:VALUE`: the document must have been last updated after or at the `VALUE` moment, accepted format are `yyyy`, `yyyy-MM` and `yyyy-MM-dd`
|
||||
* `at:VALUE`: the document must have been updated at the `VALUE` moment, accepted format are `yyyy`, `yyyy-MM` and `yyyy-MM-dd` (for `yyyy` it must be the same year, for `yyyy-MM` the same month, for `yyyy-MM-dd` the same day)
|
||||
* `uat:VALUE`: the document must have been updated at the `VALUE` moment, accepted format are `yyyy`, `yyyy-MM` and `yyyy-MM-dd` (for `yyyy` it must be the same year, for `yyyy-MM` the same month, for `yyyy-MM-dd` the same day)
|
||||
* `ubefore:VALUE`: the document must have been updated before or at the `VALUE` moment, accepted format are `yyyy`, `yyyy-MM` and `yyyy-MM-dd`
|
||||
* Language
|
||||
* `lang:VALUE`: the document must be of the specified language (example: `en`)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<label class="col-sm-2 control-label" for="inputUserUsername">{{ 'settings.user.edit.username' | translate }}</label>
|
||||
<div class="col-sm-7">
|
||||
<input name="userUsername" type="text" id="inputUserUsername" required ng-disabled="isEdit()" class="form-control"
|
||||
ng-pattern="/^[a-zA-Z0-9_@\.]*$/"
|
||||
ng-pattern="/^[a-zA-Z0-9_@.-]*$/"
|
||||
ng-minlength="3" ng-maxlength="50" ng-attr-placeholder="{{ 'settings.user.edit.username' | translate }}" ng-model="user.username"/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user