mirror of
https://github.com/sismics/docs.git
synced 2025-12-14 02:06:25 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2d7f3ebc6 | ||
|
|
8f1ff56d34 | ||
|
|
11ae0ea7d3 | ||
|
|
afa78857f9 | ||
|
|
ae2423b2e9 | ||
|
|
01d3e746d8 | ||
|
|
13cd03a762 | ||
|
|
ac7b3c4eb9 | ||
|
|
7effbc8de0 | ||
|
|
8c5f0c78e7 | ||
|
|
45e00ac93d | ||
|
|
80454afc0d | ||
|
|
428e898a7a | ||
|
|
13762eb67f | ||
|
|
04c43ebf7b | ||
|
|
f9b5a5212d | ||
|
|
0351f94761 | ||
|
|
a89543b555 | ||
|
|
ce30b1a6ff | ||
|
|
1b382004cb | ||
|
|
ab7ff25929 | ||
|
|
eedf19ad9d | ||
|
|
941ace99c6 |
4
.github/workflows/build-deploy.yml
vendored
4
.github/workflows/build-deploy.yml
vendored
@@ -19,9 +19,9 @@ jobs:
|
||||
distribution: "temurin"
|
||||
cache: maven
|
||||
- name: Install test dependencies
|
||||
run: sudo apt-get -y -q --no-install-recommends install ffmpeg mediainfo tesseract-ocr tesseract-ocr-deu
|
||||
run: sudo apt-get update && sudo apt-get -y -q --no-install-recommends install ffmpeg mediainfo tesseract-ocr tesseract-ocr-deu
|
||||
- name: Build with Maven
|
||||
run: mvn -Pprod clean install
|
||||
run: mvn --batch-mode -Pprod clean install
|
||||
- name: Upload war artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
||||
42
Dockerfile
42
Dockerfile
@@ -1,8 +1,21 @@
|
||||
FROM sismics/ubuntu-jetty:11.0.14
|
||||
FROM ubuntu:22.04
|
||||
LABEL maintainer="b.gamard@sismics.com"
|
||||
|
||||
# Run Debian in non interactive mode
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
# Configure env
|
||||
ENV LANG C.UTF-8
|
||||
ENV LC_ALL C.UTF-8
|
||||
ENV JAVA_HOME /usr/lib/jvm/java-11-openjdk-amd64/
|
||||
ENV JAVA_OPTIONS -Dfile.encoding=UTF-8 -Xmx1g
|
||||
ENV JETTY_VERSION 11.0.20
|
||||
ENV JETTY_HOME /opt/jetty
|
||||
|
||||
# Install packages
|
||||
RUN apt-get update && \
|
||||
apt-get -y -q --no-install-recommends install \
|
||||
vim less procps unzip wget tzdata openjdk-11-jdk \
|
||||
ffmpeg \
|
||||
mediainfo \
|
||||
tesseract-ocr \
|
||||
@@ -31,17 +44,32 @@ RUN apt-get update && \
|
||||
tesseract-ocr-tha \
|
||||
tesseract-ocr-tur \
|
||||
tesseract-ocr-ukr \
|
||||
tesseract-ocr-vie && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/* && \
|
||||
mkdir /app && \
|
||||
tesseract-ocr-vie \
|
||||
tesseract-ocr-sqi \
|
||||
&& apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
RUN dpkg-reconfigure -f noninteractive tzdata
|
||||
|
||||
# Install Jetty
|
||||
RUN wget -nv -O /tmp/jetty.tar.gz \
|
||||
"https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-home/${JETTY_VERSION}/jetty-home-${JETTY_VERSION}.tar.gz" \
|
||||
&& tar xzf /tmp/jetty.tar.gz -C /opt \
|
||||
&& mv /opt/jetty* /opt/jetty \
|
||||
&& useradd jetty -U -s /bin/false \
|
||||
&& chown -R jetty:jetty /opt/jetty \
|
||||
&& mkdir /opt/jetty/webapps \
|
||||
&& chmod +x /opt/jetty/bin/jetty.sh
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
# Install app
|
||||
RUN mkdir /app && \
|
||||
cd /app && \
|
||||
java -jar /opt/jetty/start.jar --add-modules=server,http,webapp,deploy
|
||||
|
||||
ADD docs.xml /app/webapps/docs.xml
|
||||
ADD docs-web/target/docs-web-*.war /app/webapps/docs.war
|
||||
|
||||
ENV JAVA_OPTIONS -Xmx1g
|
||||
|
||||
WORKDIR /app
|
||||
CMD ["java", "-jar", "/opt/jetty/start.jar"]
|
||||
|
||||
CMD ["java", "-jar", "/opt/jetty/start.jar"]
|
||||
|
||||
@@ -81,6 +81,7 @@ To build external URL, the server is expecting a `DOCS_BASE_URL` environment var
|
||||
- `DATABASE_URL`: The jdbc connection string to be used by `hibernate`.
|
||||
- `DATABASE_USER`: The user which should be used for the database connection.
|
||||
- `DATABASE_PASSWORD`: The password to be used for the database connection.
|
||||
- `DATABASE_POOL_SIZE`: The pool size to be used for the database connection.
|
||||
|
||||
- Language
|
||||
- `DOCS_DEFAULT_LANGUAGE`: The language which will be used as default. Currently supported values are:
|
||||
@@ -122,6 +123,7 @@ services:
|
||||
DATABASE_URL: "jdbc:postgresql://teedy-db:5432/teedy"
|
||||
DATABASE_USER: "teedy_db_user"
|
||||
DATABASE_PASSWORD: "teedy_db_password"
|
||||
DATABASE_POOL_SIZE: "10"
|
||||
volumes:
|
||||
- ./docs/data:/data
|
||||
networks:
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<version>1.12-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>docs-core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
@@ -17,10 +17,10 @@
|
||||
<dependencies>
|
||||
<!-- Persistence layer dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-core-jakarta</artifactId>
|
||||
<groupId>org.hibernate.orm</groupId>
|
||||
<artifactId>hibernate-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Other external dependencies -->
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
@@ -31,12 +31,12 @@
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
@@ -46,7 +46,7 @@
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-email</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.freemarker</groupId>
|
||||
<artifactId>freemarker</artifactId>
|
||||
@@ -62,46 +62,41 @@
|
||||
<artifactId>jsoup</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jul-to-slf4j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-to-slf4j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>at.favre.lib</groupId>
|
||||
<artifactId>bcrypt</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-analyzers-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-queryparser</artifactId>
|
||||
@@ -124,7 +119,12 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.api</groupId>
|
||||
<artifactId>api-all</artifactId>
|
||||
<artifactId>api-ldap-client-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.api</groupId>
|
||||
<artifactId>api-ldap-codec-standalone</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Only there to read old index and rebuild them -->
|
||||
@@ -132,22 +132,22 @@
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-backward-codecs</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.imgscalr</groupId>
|
||||
<artifactId>imgscalr-lib</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.pdfbox</groupId>
|
||||
<artifactId>pdfbox</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>fr.opensagres.xdocreport</groupId>
|
||||
<artifactId>fr.opensagres.odfdom.converter.pdf</artifactId>
|
||||
@@ -191,14 +191,14 @@
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
<profiles>
|
||||
<!-- Development profile (active by default) -->
|
||||
<profile>
|
||||
@@ -210,7 +210,7 @@
|
||||
<value>dev</value>
|
||||
</property>
|
||||
</activation>
|
||||
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
@@ -226,7 +226,7 @@
|
||||
<id>prod</id>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.sismics.docs.core.constant;
|
||||
|
||||
/**
|
||||
* Configuration parameters.
|
||||
* Configuration parameters.
|
||||
*
|
||||
* @author jtremeaux
|
||||
* @author jtremeaux
|
||||
*/
|
||||
public enum ConfigType {
|
||||
/**
|
||||
@@ -20,6 +20,11 @@ public enum ConfigType {
|
||||
*/
|
||||
GUEST_LOGIN,
|
||||
|
||||
/**
|
||||
* OCR enabled.
|
||||
*/
|
||||
OCR_ENABLED,
|
||||
|
||||
/**
|
||||
* Default language.
|
||||
*/
|
||||
|
||||
@@ -43,7 +43,7 @@ public class Constants {
|
||||
/**
|
||||
* Supported document languages.
|
||||
*/
|
||||
public static final List<String> SUPPORTED_LANGUAGES = Lists.newArrayList("eng", "fra", "ita", "deu", "spa", "por", "pol", "rus", "ukr", "ara", "hin", "chi_sim", "chi_tra", "jpn", "tha", "kor", "nld", "tur", "heb", "hun", "fin", "swe", "lav", "dan", "nor", "vie", "ces");
|
||||
public static final List<String> SUPPORTED_LANGUAGES = Lists.newArrayList("eng", "fra", "ita", "deu", "spa", "por", "pol", "rus", "ukr", "ara", "hin", "chi_sim", "chi_tra", "jpn", "tha", "kor", "nld", "tur", "heb", "hun", "fin", "swe", "lav", "dan", "nor", "vie", "ces", "sqi");
|
||||
|
||||
/**
|
||||
* Base URL environment variable.
|
||||
|
||||
@@ -87,7 +87,7 @@ public class DocumentDao {
|
||||
}
|
||||
|
||||
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_UPDATEDATE_D, d.DOC_LANGUAGE_C, ");
|
||||
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_UPDATEDATE_D, d.DOC_LANGUAGE_C, d.DOC_IDFILE_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) shareCount, ");
|
||||
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) fileCount, ");
|
||||
sb.append(" u.USE_USERNAME_C ");
|
||||
@@ -121,6 +121,7 @@ public class DocumentDao {
|
||||
documentDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
|
||||
documentDto.setUpdateTimestamp(((Timestamp) o[i++]).getTime());
|
||||
documentDto.setLanguage((String) o[i++]);
|
||||
documentDto.setFileId((String) o[i++]);
|
||||
documentDto.setShared(((Number) o[i++]).intValue() > 0);
|
||||
documentDto.setFileCount(((Number) o[i++]).intValue());
|
||||
documentDto.setCreator((String) o[i]);
|
||||
|
||||
@@ -4,13 +4,16 @@ import com.sismics.docs.core.constant.AuditLogType;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.util.AuditLogUtil;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.NoResultException;
|
||||
import jakarta.persistence.Query;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -160,6 +163,7 @@ public class FileDao {
|
||||
fileDb.setMimeType(file.getMimeType());
|
||||
fileDb.setVersionId(file.getVersionId());
|
||||
fileDb.setLatestVersion(file.isLatestVersion());
|
||||
fileDb.setSize(file.getSize());
|
||||
|
||||
return file;
|
||||
}
|
||||
@@ -212,6 +216,24 @@ public class FileDao {
|
||||
return q.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get files count by documents IDs.
|
||||
*
|
||||
* @param documentIds Documents IDs
|
||||
* @return the number of files per document id
|
||||
*/
|
||||
public Map<String, Long> countByDocumentsIds(Iterable<String> documentIds) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
Query q = em.createQuery("select f.documentId, count(*) from File f where f.documentId in :documentIds and f.latestVersion = true and f.deleteDate is null group by (f.documentId)");
|
||||
q.setParameter("documentIds", documentIds);
|
||||
Map<String, Long> result = new HashMap<>();
|
||||
q.getResultList().forEach(o -> {
|
||||
Object[] resultLine = (Object[]) o;
|
||||
result.put((String) resultLine[0], (Long) resultLine[1]);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all files from a version.
|
||||
*
|
||||
@@ -224,4 +246,12 @@ public class FileDao {
|
||||
q.setParameter("versionId", versionId);
|
||||
return q.getResultList();
|
||||
}
|
||||
|
||||
public List<File> getFilesWithUnknownSize(int limit) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
TypedQuery<File> q = em.createQuery("select f from File f where f.size = :size and f.deleteDate is null order by f.order asc", File.class);
|
||||
q.setParameter("size", File.UNKNOWN_SIZE);
|
||||
q.setMaxResults(limit);
|
||||
return q.getResultList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ public class DocumentCriteria {
|
||||
/**
|
||||
* Search query.
|
||||
*/
|
||||
private String search;
|
||||
private String simpleSearch;
|
||||
|
||||
/**
|
||||
* Full content search query.
|
||||
@@ -96,12 +96,12 @@ public class DocumentCriteria {
|
||||
this.targetIdList = targetIdList;
|
||||
}
|
||||
|
||||
public String getSearch() {
|
||||
return search;
|
||||
public String getSimpleSearch() {
|
||||
return simpleSearch;
|
||||
}
|
||||
|
||||
public void setSearch(String search) {
|
||||
this.search = search;
|
||||
public void setSimpleSearch(String search) {
|
||||
this.simpleSearch = search;
|
||||
}
|
||||
|
||||
public String getFullSearch() {
|
||||
|
||||
@@ -13,6 +13,8 @@ public class FileDeletedAsyncEvent extends UserEvent {
|
||||
*/
|
||||
private String fileId;
|
||||
|
||||
private Long fileSize;
|
||||
|
||||
public String getFileId() {
|
||||
return fileId;
|
||||
}
|
||||
@@ -21,10 +23,19 @@ public class FileDeletedAsyncEvent extends UserEvent {
|
||||
this.fileId = fileId;
|
||||
}
|
||||
|
||||
public Long getFileSize() {
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
public void setFileSize(Long fileSize) {
|
||||
this.fileSize = fileSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("fileId", fileId)
|
||||
.add("fileSize", fileSize)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ package com.sismics.docs.core.listener.async;
|
||||
|
||||
import com.google.common.eventbus.AllowConcurrentEvents;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import com.sismics.docs.core.dao.UserDao;
|
||||
import com.sismics.docs.core.event.FileDeletedAsyncEvent;
|
||||
import com.sismics.docs.core.model.context.AppContext;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import com.sismics.docs.core.util.FileUtil;
|
||||
import com.sismics.docs.core.util.TransactionUtil;
|
||||
import org.slf4j.Logger;
|
||||
@@ -11,7 +14,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Listener on file deleted.
|
||||
*
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class FileDeletedAsyncListener {
|
||||
@@ -22,7 +25,7 @@ public class FileDeletedAsyncListener {
|
||||
|
||||
/**
|
||||
* File deleted.
|
||||
*
|
||||
*
|
||||
* @param event File deleted event
|
||||
* @throws Exception e
|
||||
*/
|
||||
@@ -32,6 +35,24 @@ public class FileDeletedAsyncListener {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("File deleted event: " + event.toString());
|
||||
}
|
||||
TransactionUtil.handle(() -> {
|
||||
// Update the user quota
|
||||
UserDao userDao = new UserDao();
|
||||
User user = userDao.getById(event.getUserId());
|
||||
if (user != null) {
|
||||
Long fileSize = event.getFileSize();
|
||||
|
||||
if (fileSize.equals(File.UNKNOWN_SIZE)) {
|
||||
// The file size was not in the database, in this case we need to get from the unencrypted size.
|
||||
fileSize = FileUtil.getFileSize(event.getFileId(), user);
|
||||
}
|
||||
|
||||
if (! fileSize.equals(File.UNKNOWN_SIZE)) {
|
||||
user.setStorageCurrent(user.getStorageCurrent() - fileSize);
|
||||
userDao.updateQuota(user);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Delete the file from storage
|
||||
FileUtil.delete(event.getFileId());
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.sismics.docs.core.dao.UserDao;
|
||||
import com.sismics.docs.core.listener.async.*;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import com.sismics.docs.core.service.FileService;
|
||||
import com.sismics.docs.core.service.FileSizeService;
|
||||
import com.sismics.docs.core.service.InboxService;
|
||||
import com.sismics.docs.core.util.PdfUtil;
|
||||
import com.sismics.docs.core.util.indexing.IndexingHandler;
|
||||
@@ -65,6 +66,11 @@ public class AppContext {
|
||||
*/
|
||||
private FileService fileService;
|
||||
|
||||
/**
|
||||
* File size service.
|
||||
*/
|
||||
private FileSizeService fileSizeService;
|
||||
|
||||
/**
|
||||
* Asynchronous executors.
|
||||
*/
|
||||
@@ -102,6 +108,11 @@ public class AppContext {
|
||||
inboxService.startAsync();
|
||||
inboxService.awaitRunning();
|
||||
|
||||
// Start file size service
|
||||
fileSizeService = new FileSizeService();
|
||||
fileSizeService.startAsync();
|
||||
fileSizeService.awaitRunning();
|
||||
|
||||
// Register fonts
|
||||
PdfUtil.registerFonts();
|
||||
|
||||
@@ -238,6 +249,10 @@ public class AppContext {
|
||||
fileService.stopAsync();
|
||||
}
|
||||
|
||||
if (fileSizeService != null) {
|
||||
fileSizeService.stopAsync();
|
||||
}
|
||||
|
||||
instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,14 @@ public class File implements Loggable {
|
||||
@Column(name = "FIL_LATESTVERSION_B", nullable = false)
|
||||
private boolean latestVersion;
|
||||
|
||||
public static final Long UNKNOWN_SIZE = -1L;
|
||||
|
||||
/**
|
||||
* Can be {@link File#UNKNOWN_SIZE} if the size has not been stored in the database when the file has been uploaded
|
||||
*/
|
||||
@Column(name = "FIL_SIZE_N", nullable = false)
|
||||
private Long size;
|
||||
|
||||
/**
|
||||
* Private key to decrypt the file.
|
||||
* Not saved to database, of course.
|
||||
@@ -204,6 +212,18 @@ public class File implements Loggable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can return {@link File#UNKNOWN_SIZE} if the file size is not stored in the database.
|
||||
*/
|
||||
public Long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public File setSize(Long size) {
|
||||
this.size = size;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.sismics.docs.core.service;
|
||||
|
||||
import com.google.common.util.concurrent.AbstractScheduledService;
|
||||
import com.sismics.docs.core.dao.FileDao;
|
||||
import com.sismics.docs.core.dao.UserDao;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import com.sismics.docs.core.util.FileUtil;
|
||||
import com.sismics.docs.core.util.TransactionUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Service that retrieve files sizes when they are not in the database.
|
||||
*/
|
||||
public class FileSizeService extends AbstractScheduledService {
|
||||
/**
|
||||
* Logger.
|
||||
*/
|
||||
private static final Logger log = LoggerFactory.getLogger(FileSizeService.class);
|
||||
|
||||
public FileSizeService() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startUp() {
|
||||
log.info("File size service starting up");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutDown() {
|
||||
log.info("File size service shutting down");
|
||||
}
|
||||
|
||||
private static final int BATCH_SIZE = 30;
|
||||
|
||||
@Override
|
||||
protected void runOneIteration() {
|
||||
try {
|
||||
TransactionUtil.handle(() -> {
|
||||
FileDao fileDao = new FileDao();
|
||||
List<File> files = fileDao.getFilesWithUnknownSize(BATCH_SIZE);
|
||||
for(File file : files) {
|
||||
processFile(file);
|
||||
}
|
||||
if(files.size() < BATCH_SIZE) {
|
||||
log.info("No more file to process, stopping the service");
|
||||
stopAsync();
|
||||
}
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
log.error("Exception during file service iteration", e);
|
||||
}
|
||||
}
|
||||
|
||||
void processFile(File file) {
|
||||
UserDao userDao = new UserDao();
|
||||
User user = userDao.getById(file.getUserId());
|
||||
if(user == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
long fileSize = FileUtil.getFileSize(file.getId(), user);
|
||||
if(fileSize != File.UNKNOWN_SIZE){
|
||||
FileDao fileDao = new FileDao();
|
||||
file.setSize(fileSize);
|
||||
fileDao.update(file);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Scheduler scheduler() {
|
||||
return Scheduler.newFixedDelaySchedule(0, 1, TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
package com.sismics.docs.core.service;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.util.concurrent.AbstractScheduledService;
|
||||
import com.sismics.docs.core.constant.ConfigType;
|
||||
import com.sismics.docs.core.dao.TagDao;
|
||||
import com.sismics.docs.core.dao.criteria.TagCriteria;
|
||||
import com.sismics.docs.core.dao.dto.TagDto;
|
||||
import com.sismics.docs.core.event.DocumentCreatedAsyncEvent;
|
||||
import com.sismics.docs.core.model.jpa.Config;
|
||||
import com.sismics.docs.core.model.jpa.Document;
|
||||
import com.sismics.docs.core.model.jpa.Tag;
|
||||
import com.sismics.docs.core.util.ConfigUtil;
|
||||
|
||||
@@ -8,13 +8,12 @@ import java.util.ResourceBundle;
|
||||
|
||||
/**
|
||||
* Configuration parameter utilities.
|
||||
*
|
||||
* @author jtremeaux
|
||||
*
|
||||
*/
|
||||
public class ConfigUtil {
|
||||
/**
|
||||
* Returns the textual value of a configuration parameter.
|
||||
*
|
||||
*
|
||||
* @param configType Type of the configuration parameter
|
||||
* @return Textual value of the configuration parameter
|
||||
* @throws IllegalStateException Configuration parameter undefined
|
||||
@@ -30,7 +29,7 @@ public class ConfigUtil {
|
||||
|
||||
/**
|
||||
* Returns the configuration resource bundle.
|
||||
*
|
||||
*
|
||||
* @return Resource bundle
|
||||
*/
|
||||
public static ResourceBundle getConfigBundle() {
|
||||
@@ -39,14 +38,14 @@ public class ConfigUtil {
|
||||
|
||||
/**
|
||||
* Returns the integer value of a configuration parameter.
|
||||
*
|
||||
*
|
||||
* @param configType Type of the configuration parameter
|
||||
* @return Integer value of the configuration parameter
|
||||
* @throws IllegalStateException Configuration parameter undefined
|
||||
*/
|
||||
public static int getConfigIntegerValue(ConfigType configType) {
|
||||
String value = getConfigStringValue(configType);
|
||||
|
||||
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
|
||||
@@ -65,14 +64,28 @@ public class ConfigUtil {
|
||||
|
||||
/**
|
||||
* Returns the boolean value of a configuration parameter.
|
||||
*
|
||||
*
|
||||
* @param configType Type of the configuration parameter
|
||||
* @return Boolean value of the configuration parameter
|
||||
* @throws IllegalStateException Configuration parameter undefined
|
||||
*/
|
||||
public static boolean getConfigBooleanValue(ConfigType configType) {
|
||||
String value = getConfigStringValue(configType);
|
||||
|
||||
return Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the boolean value of a configuration parameter with a default value.
|
||||
*
|
||||
* @param configType Type of the configuration parameter
|
||||
* @param defaultValue Default value to return if the configuration parameter is undefined
|
||||
* @return Boolean value of the configuration parameter
|
||||
*/
|
||||
public static boolean getConfigBooleanValue(ConfigType configType, boolean defaultValue) {
|
||||
try {
|
||||
return getConfigBooleanValue(configType);
|
||||
} catch (IllegalStateException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ import com.sismics.util.Scalr;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
import com.sismics.util.io.InputStreamReaderThread;
|
||||
import com.sismics.util.mime.MimeTypeUtil;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.io.input.CountingInputStream;
|
||||
import org.apache.commons.io.output.NullOutputStream;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -46,7 +49,7 @@ public class FileUtil {
|
||||
/**
|
||||
* File ID of files currently being processed.
|
||||
*/
|
||||
private static Set<String> processingFileSet = Collections.synchronizedSet(new HashSet<>());
|
||||
private static final Set<String> processingFileSet = Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
/**
|
||||
* Optical character recognition on an image.
|
||||
@@ -149,6 +152,7 @@ public class FileUtil {
|
||||
file.setName(StringUtils.abbreviate(name, 200));
|
||||
file.setMimeType(mimeType);
|
||||
file.setUserId(userId);
|
||||
file.setSize(fileSize);
|
||||
|
||||
// Get files of this document
|
||||
FileDao fileDao = new FileDao();
|
||||
@@ -240,4 +244,31 @@ public class FileUtil {
|
||||
public static boolean isProcessingFile(String fileId) {
|
||||
return processingFileSet.contains(fileId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of a file on disk.
|
||||
*
|
||||
* @param fileId the file id
|
||||
* @param user the file owner
|
||||
* @return the size or -1 if something went wrong
|
||||
*/
|
||||
public static long getFileSize(String fileId, User user) {
|
||||
// To get the size we copy the decrypted content into a null output stream
|
||||
// and count the copied byte size.
|
||||
Path storedFile = DirectoryUtil.getStorageDirectory().resolve(fileId);
|
||||
if (! Files.exists(storedFile)) {
|
||||
log.debug("File does not exist " + fileId);
|
||||
return File.UNKNOWN_SIZE;
|
||||
}
|
||||
try (InputStream fileInputStream = Files.newInputStream(storedFile);
|
||||
InputStream inputStream = EncryptionUtil.decryptInputStream(fileInputStream, user.getPrivateKey());
|
||||
CountingInputStream countingInputStream = new CountingInputStream(inputStream);
|
||||
) {
|
||||
IOUtils.copy(countingInputStream, NullOutputStream.NULL_OUTPUT_STREAM);
|
||||
return countingInputStream.getByteCount();
|
||||
} catch (Exception e) {
|
||||
log.debug("Can't find size of file " + fileId, e);
|
||||
return File.UNKNOWN_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ public class LdapAuthenticationHandler implements AuthenticationHandler {
|
||||
if (ldapConnection == null) {
|
||||
return null;
|
||||
}
|
||||
ldapConnection.bind();
|
||||
|
||||
EntryCursor cursor = ldapConnection.search(ConfigUtil.getConfigStringValue(ConfigType.LDAP_BASE_DN),
|
||||
ConfigUtil.getConfigStringValue(ConfigType.LDAP_FILTER).replace("USERNAME", username), SearchScope.SUBTREE);
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.sismics.docs.core.util.format;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.sismics.util.ClasspathScanner;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.sismics.docs.core.util.format;
|
||||
import com.google.common.io.Closer;
|
||||
import com.sismics.docs.core.constant.Constants;
|
||||
import com.sismics.docs.core.util.FileUtil;
|
||||
import com.sismics.docs.core.util.ConfigUtil;
|
||||
import com.sismics.docs.core.constant.ConfigType;
|
||||
import com.sismics.util.mime.MimeType;
|
||||
import org.apache.pdfbox.io.MemoryUsageSetting;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
@@ -22,7 +24,6 @@ import java.nio.file.Path;
|
||||
/**
|
||||
* Image format handler.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class ImageFormatHandler implements FormatHandler {
|
||||
/**
|
||||
@@ -45,7 +46,7 @@ public class ImageFormatHandler implements FormatHandler {
|
||||
|
||||
@Override
|
||||
public String extractContent(String language, Path file) throws Exception {
|
||||
if (language == null) {
|
||||
if (language == null || !ConfigUtil.getConfigBooleanValue(ConfigType.OCR_ENABLED, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.sismics.docs.core.util.format;
|
||||
|
||||
import com.google.common.io.Closer;
|
||||
import com.sismics.docs.core.util.FileUtil;
|
||||
import com.sismics.docs.core.util.ConfigUtil;
|
||||
import com.sismics.docs.core.constant.ConfigType;
|
||||
import com.sismics.util.mime.MimeType;
|
||||
import org.apache.pdfbox.io.MemoryUsageSetting;
|
||||
import org.apache.pdfbox.multipdf.PDFMergerUtility;
|
||||
@@ -53,7 +55,7 @@ public class PdfFormatHandler implements FormatHandler {
|
||||
}
|
||||
|
||||
// No text content, try to OCR it
|
||||
if (language != null && content != null && content.trim().isEmpty()) {
|
||||
if (language != null && content != null && content.trim().isEmpty() && ConfigUtil.getConfigBooleanValue(ConfigType.OCR_ENABLED, true)) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try (InputStream inputStream = Files.newInputStream(file);
|
||||
PDDocument pdfDocument = PDDocument.load(inputStream)) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.sismics.docs.core.util.format;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.io.Closer;
|
||||
import com.lowagie.text.*;
|
||||
import com.lowagie.text.pdf.PdfWriter;
|
||||
|
||||
@@ -26,9 +26,18 @@ import org.apache.lucene.analysis.standard.StandardAnalyzer;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.StringField;
|
||||
import org.apache.lucene.document.TextField;
|
||||
import org.apache.lucene.index.*;
|
||||
import org.apache.lucene.index.CheckIndex;
|
||||
import org.apache.lucene.index.ConcurrentMergeScheduler;
|
||||
import org.apache.lucene.index.DirectoryReader;
|
||||
import org.apache.lucene.index.IndexWriter;
|
||||
import org.apache.lucene.index.IndexWriterConfig;
|
||||
import org.apache.lucene.index.Term;
|
||||
import org.apache.lucene.queryparser.simple.SimpleQueryParser;
|
||||
import org.apache.lucene.search.*;
|
||||
import org.apache.lucene.search.BooleanClause;
|
||||
import org.apache.lucene.search.BooleanQuery;
|
||||
import org.apache.lucene.search.IndexSearcher;
|
||||
import org.apache.lucene.search.ScoreDoc;
|
||||
import org.apache.lucene.search.TopDocs;
|
||||
import org.apache.lucene.search.highlight.Highlighter;
|
||||
import org.apache.lucene.search.highlight.QueryScorer;
|
||||
import org.apache.lucene.search.highlight.SimpleHTMLEncoder;
|
||||
@@ -47,7 +56,12 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Lucene indexing handler.
|
||||
@@ -242,34 +256,28 @@ public class LuceneIndexingHandler implements IndexingHandler {
|
||||
|
||||
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, d.DOC_IDFILE_C, ");
|
||||
sb.append(" s.count c5, ");
|
||||
sb.append(" f.count c6, ");
|
||||
sb.append(" rs2.RTP_ID_C c7, rs2.RTP_NAME_C, d.DOC_UPDATEDATE_D c8 ");
|
||||
sb.append(" from T_DOCUMENT d ");
|
||||
sb.append(" left join (SELECT count(s.SHA_ID_C) count, ac.ACL_SOURCEID_C " +
|
||||
" FROM T_SHARE s, T_ACL ac " +
|
||||
" WHERE ac.ACL_TARGETID_C = s.SHA_ID_C AND ac.ACL_DELETEDATE_D IS NULL AND " +
|
||||
" s.SHA_DELETEDATE_D IS NULL group by ac.ACL_SOURCEID_C) s on s.ACL_SOURCEID_C = d.DOC_ID_C " +
|
||||
" left join (SELECT count(f.FIL_ID_C) count, f.FIL_IDDOC_C " +
|
||||
" FROM T_FILE f " +
|
||||
" WHERE f.FIL_DELETEDATE_D is null group by f.FIL_IDDOC_C) f on f.FIL_IDDOC_C = d.DOC_ID_C ");
|
||||
" s.SHA_DELETEDATE_D IS NULL group by ac.ACL_SOURCEID_C) s on s.ACL_SOURCEID_C = d.DOC_ID_C ");
|
||||
sb.append(" left join (select rs.*, rs3.idDocument " +
|
||||
"from T_ROUTE_STEP rs " +
|
||||
"join (select r.RTE_IDDOCUMENT_C idDocument, rs.RTP_IDROUTE_C idRoute, min(rs.RTP_ORDER_N) minOrder from T_ROUTE_STEP rs join T_ROUTE r on r.RTE_ID_C = rs.RTP_IDROUTE_C and r.RTE_DELETEDATE_D is null where rs.RTP_DELETEDATE_D is null and rs.RTP_ENDDATE_D is null group by rs.RTP_IDROUTE_C, r.RTE_IDDOCUMENT_C) rs3 on rs.RTP_IDROUTE_C = rs3.idRoute and rs.RTP_ORDER_N = rs3.minOrder " +
|
||||
"where rs.RTP_IDTARGET_C in (:targetIdList)) rs2 on rs2.idDocument = d.DOC_ID_C ");
|
||||
|
||||
// Add search criterias
|
||||
if (criteria.getTargetIdList() != null) {
|
||||
if (!SecurityUtil.skipAclCheck(criteria.getTargetIdList())) {
|
||||
// Read permission is enough for searching
|
||||
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 (!SecurityUtil.skipAclCheck(criteria.getTargetIdList())) {
|
||||
// Read permission is enough for searching
|
||||
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)");
|
||||
}
|
||||
if (!Strings.isNullOrEmpty(criteria.getSearch()) || !Strings.isNullOrEmpty(criteria.getFullSearch())) {
|
||||
documentSearchMap = search(criteria.getSearch(), criteria.getFullSearch());
|
||||
parameterMap.put("targetIdList", criteria.getTargetIdList());
|
||||
if (!Strings.isNullOrEmpty(criteria.getSimpleSearch()) || !Strings.isNullOrEmpty(criteria.getFullSearch())) {
|
||||
documentSearchMap = search(criteria.getSimpleSearch(), criteria.getFullSearch());
|
||||
if (documentSearchMap.isEmpty()) {
|
||||
// If the search doesn't find any document, the request should return nothing
|
||||
documentSearchMap.put(UUID.randomUUID().toString(), null);
|
||||
@@ -312,7 +320,7 @@ public class LuceneIndexingHandler implements IndexingHandler {
|
||||
criteriaList.add("(" + Joiner.on(" OR ").join(tagCriteriaList) + ")");
|
||||
}
|
||||
}
|
||||
if (criteria.getExcludedTagIdList() != null && !criteria.getExcludedTagIdList().isEmpty()) {
|
||||
if (!criteria.getExcludedTagIdList().isEmpty()) {
|
||||
int index = 0;
|
||||
for (List<String> tagIdList : criteria.getExcludedTagIdList()) {
|
||||
List<String> tagCriteriaList = Lists.newArrayList();
|
||||
@@ -367,8 +375,6 @@ public class LuceneIndexingHandler implements IndexingHandler {
|
||||
documentDto.setFileId((String) o[i++]);
|
||||
Number shareCount = (Number) o[i++];
|
||||
documentDto.setShared(shareCount != null && shareCount.intValue() > 0);
|
||||
Number fileCount = (Number) o[i++];
|
||||
documentDto.setFileCount(fileCount == null ? 0 : fileCount.intValue());
|
||||
documentDto.setActiveRoute(o[i++] != null);
|
||||
documentDto.setCurrentStepName((String) o[i++]);
|
||||
documentDto.setUpdateTimestamp(((Timestamp) o[i]).getTime());
|
||||
@@ -406,14 +412,14 @@ public class LuceneIndexingHandler implements IndexingHandler {
|
||||
/**
|
||||
* Fulltext search in files and documents.
|
||||
*
|
||||
* @param searchQuery Search query on metadatas
|
||||
* @param simpleSearchQuery Search query on metadatas
|
||||
* @param fullSearchQuery Search query on all fields
|
||||
* @return Map of document IDs as key and highlight as value
|
||||
* @throws Exception e
|
||||
*/
|
||||
private Map<String, String> search(String searchQuery, String fullSearchQuery) throws Exception {
|
||||
private Map<String, String> search(String simpleSearchQuery, String fullSearchQuery) throws Exception {
|
||||
// The fulltext query searches in all fields
|
||||
searchQuery = searchQuery + " " + fullSearchQuery;
|
||||
String searchQuery = simpleSearchQuery + " " + fullSearchQuery;
|
||||
|
||||
// Build search query
|
||||
Analyzer analyzer = new StandardAnalyzer();
|
||||
|
||||
@@ -68,7 +68,7 @@ public class PaginatedLists {
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a query and returns the data of the currunt page.
|
||||
* Executes a query and returns the data of the current page.
|
||||
*
|
||||
* @param paginatedList Paginated list object containing parameters, and into which results are added by side effects
|
||||
* @param queryParam Query parameters
|
||||
@@ -82,18 +82,6 @@ public class PaginatedLists {
|
||||
q.setMaxResults(paginatedList.getLimit());
|
||||
return q.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a paginated request with 2 native queries (one to count the number of results, and one to return the page).
|
||||
*
|
||||
* @param paginatedList Paginated list object containing parameters, and into which results are added by side effects
|
||||
* @param queryParam Query parameters
|
||||
* @return List of results
|
||||
*/
|
||||
public static <E> List<Object[]> executePaginatedQuery(PaginatedList<E> paginatedList, QueryParam queryParam) {
|
||||
executeCountQuery(paginatedList, queryParam);
|
||||
return executeResultQuery(paginatedList, queryParam);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a paginated request with 2 native queries (one to count the number of results, and one to return the page).
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package com.sismics.util.io;
|
||||
|
||||
import com.google.common.io.Closer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@@ -16,7 +15,7 @@ import java.io.InputStreamReader;
|
||||
*/
|
||||
public class InputStreamReaderThread extends Thread {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(InputStreamReaderThread.class);
|
||||
private static final Logger logger = Logger.getLogger(InputStreamReaderThread.class);
|
||||
|
||||
private InputStream is;
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ abstract class DbOpenHelper {
|
||||
private static final Logger log = LoggerFactory.getLogger(DbOpenHelper.class);
|
||||
|
||||
private final JdbcConnectionAccess jdbcConnectionAccess;
|
||||
|
||||
|
||||
private final List<Exception> exceptions = new ArrayList<>();
|
||||
|
||||
private Formatter formatter;
|
||||
@@ -99,7 +99,7 @@ abstract class DbOpenHelper {
|
||||
onCreate();
|
||||
oldVersion = 0;
|
||||
}
|
||||
|
||||
|
||||
// Execute update script
|
||||
ResourceBundle configBundle = ConfigUtil.getConfigBundle();
|
||||
Integer currentVersion = Integer.parseInt(configBundle.getString("db.version"));
|
||||
@@ -126,7 +126,7 @@ abstract class DbOpenHelper {
|
||||
|
||||
/**
|
||||
* Execute all upgrade scripts in ascending order for a given version.
|
||||
*
|
||||
*
|
||||
* @param version Version number
|
||||
* @throws Exception e
|
||||
*/
|
||||
@@ -136,7 +136,7 @@ abstract class DbOpenHelper {
|
||||
return name.matches("dbupdate-" + versionString + "-\\d+\\.sql");
|
||||
});
|
||||
Collections.sort(fileNameList);
|
||||
|
||||
|
||||
for (String fileName : fileNameList) {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info(MessageFormat.format("Executing script: {0}", fileName));
|
||||
@@ -145,16 +145,16 @@ abstract class DbOpenHelper {
|
||||
executeScript(is);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Execute a SQL script. All statements must be one line only.
|
||||
*
|
||||
*
|
||||
* @param inputScript Script to execute
|
||||
* @throws IOException e
|
||||
*/
|
||||
private void executeScript(InputStream inputScript) throws IOException {
|
||||
List<String> lines = CharStreams.readLines(new InputStreamReader(inputScript));
|
||||
|
||||
|
||||
for (String sql : lines) {
|
||||
if (Strings.isNullOrEmpty(sql) || sql.startsWith("--")) {
|
||||
continue;
|
||||
@@ -178,13 +178,13 @@ abstract class DbOpenHelper {
|
||||
}
|
||||
|
||||
public abstract void onCreate() throws Exception;
|
||||
|
||||
|
||||
public abstract void onUpgrade(int oldVersion, int newVersion) throws Exception;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a List of all Exceptions which occured during the export.
|
||||
* Returns a List of all Exceptions which occurred during the export.
|
||||
*
|
||||
* @return A List containig the Exceptions occured during the export
|
||||
* @return A List containing the Exceptions occurred during the export
|
||||
*/
|
||||
public List<?> getExceptions() {
|
||||
return exceptions;
|
||||
@@ -192,7 +192,7 @@ abstract class DbOpenHelper {
|
||||
|
||||
/**
|
||||
* Format the output SQL statements.
|
||||
*
|
||||
*
|
||||
* @param format True to format
|
||||
*/
|
||||
public void setFormat(boolean format) {
|
||||
|
||||
@@ -26,7 +26,7 @@ import java.util.Properties;
|
||||
public final class EMF {
|
||||
private static final Logger log = LoggerFactory.getLogger(EMF.class);
|
||||
|
||||
private static Map<Object, Object> properties;
|
||||
private static Properties properties;
|
||||
|
||||
private static EntityManagerFactory emfInstance;
|
||||
|
||||
@@ -59,7 +59,7 @@ public final class EMF {
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<Object, Object> getEntityManagerProperties() {
|
||||
private static Properties getEntityManagerProperties() {
|
||||
// Use properties file if exists
|
||||
try {
|
||||
URL hibernatePropertiesUrl = EMF.class.getResource("/hibernate.properties");
|
||||
@@ -79,9 +79,13 @@ public final class EMF {
|
||||
String databaseUrl = System.getenv("DATABASE_URL");
|
||||
String databaseUsername = System.getenv("DATABASE_USER");
|
||||
String databasePassword = System.getenv("DATABASE_PASSWORD");
|
||||
String databasePoolSize = System.getenv("DATABASE_POOL_SIZE");
|
||||
if(databasePoolSize == null) {
|
||||
databasePoolSize = "10";
|
||||
}
|
||||
|
||||
log.info("Configuring EntityManager from environment parameters");
|
||||
Map<Object, Object> props = new HashMap<>();
|
||||
Properties props = new Properties();
|
||||
Path dbDirectory = DirectoryUtil.getDbDirectory();
|
||||
String dbFile = dbDirectory.resolve("docs").toAbsolutePath().toString();
|
||||
if (Strings.isNullOrEmpty(databaseUrl)) {
|
||||
@@ -92,7 +96,7 @@ public final class EMF {
|
||||
props.put("hibernate.connection.username", "sa");
|
||||
} else {
|
||||
props.put("hibernate.connection.driver_class", "org.postgresql.Driver");
|
||||
props.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL94Dialect");
|
||||
props.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
|
||||
props.put("hibernate.connection.url", databaseUrl);
|
||||
props.put("hibernate.connection.username", databaseUsername);
|
||||
props.put("hibernate.connection.password", databasePassword);
|
||||
@@ -103,7 +107,7 @@ public final class EMF {
|
||||
props.put("hibernate.max_fetch_depth", "5");
|
||||
props.put("hibernate.cache.use_second_level_cache", "false");
|
||||
props.put("hibernate.connection.initial_pool_size", "1");
|
||||
props.put("hibernate.connection.pool_size", "10");
|
||||
props.put("hibernate.connection.pool_size", databasePoolSize);
|
||||
props.put("hibernate.connection.pool_validation_interval", "5");
|
||||
return props;
|
||||
}
|
||||
@@ -136,4 +140,4 @@ public final class EMF {
|
||||
public static String getDriver() {
|
||||
return (String) properties.get("hibernate.connection.driver_class");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.sismics.util.logback;
|
||||
package com.sismics.util.log4j;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.log4j.Level;
|
||||
|
||||
/**
|
||||
* Log search criteria.
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.sismics.util.logback;
|
||||
package com.sismics.util.log4j;
|
||||
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import org.apache.log4j.Level;
|
||||
|
||||
/**
|
||||
* Log entry.
|
||||
@@ -1,22 +1,25 @@
|
||||
package com.sismics.util.logback;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.AppenderBase;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedList;
|
||||
package com.sismics.util.log4j;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import org.apache.log4j.AppenderSkeleton;
|
||||
import org.apache.log4j.Level;
|
||||
import org.apache.log4j.helpers.LogLog;
|
||||
import org.apache.log4j.spi.LoggingEvent;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedList;
|
||||
|
||||
/**
|
||||
* Memory appender for Logback.
|
||||
* Memory appender for Log4J.
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
public class MemoryAppender extends AppenderBase<ILoggingEvent> {
|
||||
public class MemoryAppender extends AppenderSkeleton {
|
||||
|
||||
/**
|
||||
* Maximum size of the queue.
|
||||
@@ -26,18 +29,34 @@ public class MemoryAppender extends AppenderBase<ILoggingEvent> {
|
||||
/**
|
||||
* Queue of log entries.
|
||||
*/
|
||||
private static final Queue<LogEntry> logQueue = new ConcurrentLinkedQueue<>();
|
||||
private final Queue<LogEntry> logQueue = new ConcurrentLinkedQueue<LogEntry>();
|
||||
|
||||
@Override
|
||||
protected void append(ILoggingEvent event) {
|
||||
public boolean requiresLayout() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void append(LoggingEvent event) {
|
||||
while (logQueue.size() > size) {
|
||||
logQueue.remove();
|
||||
}
|
||||
|
||||
|
||||
if (closed) {
|
||||
LogLog.warn("This appender is already closed, cannot append event.");
|
||||
return;
|
||||
}
|
||||
|
||||
String loggerName = getLoggerName(event);
|
||||
|
||||
LogEntry logEntry = new LogEntry(System.currentTimeMillis(), event.getLevel(), loggerName, event.getMessage());
|
||||
LogEntry logEntry = new LogEntry(System.currentTimeMillis(), event.getLevel(), loggerName, event.getMessage().toString());
|
||||
logQueue.add(logEntry);
|
||||
}
|
||||
|
||||
@@ -47,7 +66,7 @@ public class MemoryAppender extends AppenderBase<ILoggingEvent> {
|
||||
* @param event Event
|
||||
* @return Class name
|
||||
*/
|
||||
private String getLoggerName(ILoggingEvent event) {
|
||||
private String getLoggerName(LoggingEvent event) {
|
||||
int index = event.getLoggerName().lastIndexOf('.');
|
||||
|
||||
return (index > -1) ?
|
||||
@@ -56,12 +75,12 @@ public class MemoryAppender extends AppenderBase<ILoggingEvent> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of size.
|
||||
* Getter of logList.
|
||||
*
|
||||
* @return size
|
||||
* @return logList
|
||||
*/
|
||||
public int getSize() {
|
||||
return size;
|
||||
public Queue<LogEntry> getLogList() {
|
||||
return logQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,14 +98,15 @@ public class MemoryAppender extends AppenderBase<ILoggingEvent> {
|
||||
* @param criteria Search criteria
|
||||
* @param list Paginated list (modified by side effect)
|
||||
*/
|
||||
public static void find(LogCriteria criteria, PaginatedList<LogEntry> list) {
|
||||
public void find(LogCriteria criteria, PaginatedList<LogEntry> list) {
|
||||
List<LogEntry> logEntryList = new LinkedList<LogEntry>();
|
||||
final Level minLevel = criteria.getMinLevel();
|
||||
final String tag = criteria.getTag();
|
||||
final String message = criteria.getMessage();
|
||||
int resultCount = 0;
|
||||
for (LogEntry logEntry : logQueue) {
|
||||
if ((minLevel == null || logEntry.getLevel().toInt() >= minLevel.toInt()) &&
|
||||
for (Iterator<LogEntry> it = logQueue.iterator(); it.hasNext();) {
|
||||
LogEntry logEntry = it.next();
|
||||
if ((minLevel == null || Integer.compare(logEntry.getLevel().toInt(), minLevel.toInt()) >= 0) &&
|
||||
(tag == null || logEntry.getTag().toLowerCase().equals(tag)) &&
|
||||
(message == null || logEntry.getMessage().toLowerCase().contains(message))) {
|
||||
logEntryList.add(logEntry);
|
||||
@@ -1 +1 @@
|
||||
db.version=28
|
||||
db.version=31
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table T_FILE add column FIL_SIZE_N bigint not null default -1;
|
||||
update T_CONFIG set CFG_VALUE_C = '29' where CFG_ID_C = 'DB_VERSION';
|
||||
@@ -0,0 +1,2 @@
|
||||
create index IDX_FIL_IDDOC_C ON T_FILE (FIL_IDDOC_C ASC);
|
||||
update T_CONFIG set CFG_VALUE_C = '30' where CFG_ID_C = 'DB_VERSION';
|
||||
@@ -0,0 +1,7 @@
|
||||
-- DBUPDATE-031-0.SQL
|
||||
|
||||
-- Insert a new setting for OCR recognition
|
||||
insert into T_CONFIG (CFG_ID_C, CFG_VALUE_C) values ('OCR_ENABLED', 'true');
|
||||
|
||||
-- Update the database version
|
||||
update T_CONFIG set CFG_VALUE_C = '31' where CFG_ID_C = 'DB_VERSION';
|
||||
49
docs-core/src/test/java/com/sismics/BaseTest.java
Normal file
49
docs-core/src/test/java/com/sismics/BaseTest.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package com.sismics;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
public abstract class BaseTest {
|
||||
|
||||
protected static final String FILE_CSV = "document.csv";
|
||||
|
||||
protected static final String FILE_DOCX = "document.docx";
|
||||
|
||||
protected static final String FILE_GIF = "image.gif";
|
||||
|
||||
protected static final String FILE_JPG = "apollo_portrait.jpg";
|
||||
|
||||
protected static final Long FILE_JPG_SIZE = 7_907L;
|
||||
|
||||
protected static final String FILE_JPG2 = "apollo_landscape.jpg";
|
||||
|
||||
protected static final String FILE_MP4 = "video.mp4";
|
||||
|
||||
protected static final String FILE_ODT = "document.odt";
|
||||
|
||||
protected static final String FILE_PDF = "udhr.pdf";
|
||||
|
||||
protected static final String FILE_PDF_ENCRYPTED = "udhr_encrypted.pdf";
|
||||
|
||||
protected static final String FILE_PDF_SCANNED = "scanned.pdf";
|
||||
|
||||
protected static final String FILE_PNG = "image.png";
|
||||
|
||||
protected static final String FILE_PPTX = "apache.pptx";
|
||||
|
||||
protected static final String FILE_TXT = "document.txt";
|
||||
|
||||
protected static final String FILE_WEBM = "video.webm";
|
||||
|
||||
protected static final String FILE_XLSX = "document.xlsx";
|
||||
|
||||
protected static final String FILE_ZIP = "document.zip";
|
||||
|
||||
protected static URL getResource(String fileName) {
|
||||
return ClassLoader.getSystemResource("file/" + fileName);
|
||||
}
|
||||
|
||||
protected static InputStream getSystemResourceAsStream(String fileName) {
|
||||
return ClassLoader.getSystemResourceAsStream("file/" + fileName);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,36 @@
|
||||
package com.sismics.docs;
|
||||
|
||||
import com.sismics.BaseTest;
|
||||
import com.sismics.docs.core.dao.FileDao;
|
||||
import com.sismics.docs.core.dao.UserDao;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import com.sismics.docs.core.util.DirectoryUtil;
|
||||
import com.sismics.docs.core.util.EncryptionUtil;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
import com.sismics.util.jpa.EMF;
|
||||
import com.sismics.util.mime.MimeType;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityTransaction;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
||||
|
||||
/**
|
||||
* Base class of tests with a transactional context.
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
public abstract class BaseTransactionalTest {
|
||||
public abstract class BaseTransactionalTest extends BaseTest {
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
public void setUp() {
|
||||
// Initialize the entity manager
|
||||
EntityManager em = EMF.get().createEntityManager();
|
||||
ThreadLocalContext context = ThreadLocalContext.get();
|
||||
@@ -25,6 +40,35 @@ public abstract class BaseTransactionalTest {
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
public void tearDown() {
|
||||
ThreadLocalContext.get().getEntityManager().getTransaction().rollback();
|
||||
}
|
||||
|
||||
protected User createUser(String userName) throws Exception {
|
||||
UserDao userDao = new UserDao();
|
||||
User user = new User();
|
||||
user.setUsername(userName);
|
||||
user.setPassword("12345678");
|
||||
user.setEmail("toto@docs.com");
|
||||
user.setRoleId("admin");
|
||||
user.setStorageQuota(100_000L);
|
||||
userDao.create(user, userName);
|
||||
return user;
|
||||
}
|
||||
|
||||
protected File createFile(User user, long fileSize) throws Exception {
|
||||
FileDao fileDao = new FileDao();
|
||||
try(InputStream inputStream = getSystemResourceAsStream(FILE_JPG)) {
|
||||
File file = new File();
|
||||
file.setId("apollo_portrait");
|
||||
file.setUserId(user.getId());
|
||||
file.setVersion(0);
|
||||
file.setMimeType(MimeType.IMAGE_JPEG);
|
||||
file.setSize(fileSize);
|
||||
String fileId = fileDao.create(file, user.getId());
|
||||
Cipher cipher = EncryptionUtil.getEncryptionCipher(user.getPrivateKey());
|
||||
Files.copy(new CipherInputStream(inputStream, cipher), DirectoryUtil.getStorageDirectory().resolve(fileId), REPLACE_EXISTING);
|
||||
return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,22 +18,20 @@ public class TestJpa extends BaseTransactionalTest {
|
||||
public void testJpa() throws Exception {
|
||||
// Create a user
|
||||
UserDao userDao = new UserDao();
|
||||
User user = new User();
|
||||
user.setUsername("username");
|
||||
user.setPassword("12345678");
|
||||
user.setEmail("toto@docs.com");
|
||||
user.setRoleId("admin");
|
||||
user.setStorageQuota(10L);
|
||||
String id = userDao.create(user, "me");
|
||||
|
||||
User user = createUser("testJpa");
|
||||
|
||||
TransactionUtil.commit();
|
||||
|
||||
// Search a user by his ID
|
||||
user = userDao.getById(id);
|
||||
user = userDao.getById(user.getId());
|
||||
Assert.assertNotNull(user);
|
||||
Assert.assertEquals("toto@docs.com", user.getEmail());
|
||||
|
||||
// Authenticate using the database
|
||||
Assert.assertNotNull(new InternalAuthenticationHandler().authenticate("username", "12345678"));
|
||||
Assert.assertNotNull(new InternalAuthenticationHandler().authenticate("testJpa", "12345678"));
|
||||
|
||||
// Delete the created user
|
||||
userDao.delete("testJpa", user.getId());
|
||||
TransactionUtil.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.sismics.docs.core.listener.async;
|
||||
|
||||
import com.sismics.docs.BaseTransactionalTest;
|
||||
import com.sismics.docs.core.dao.UserDao;
|
||||
import com.sismics.docs.core.event.FileDeletedAsyncEvent;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import com.sismics.docs.core.util.TransactionUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class FileDeletedAsyncListenerTest extends BaseTransactionalTest {
|
||||
|
||||
@Test
|
||||
public void updateQuotaSizeKnown() throws Exception {
|
||||
User user = createUser("updateQuotaSizeKnown");
|
||||
File file = createFile(user, FILE_JPG_SIZE);
|
||||
UserDao userDao = new UserDao();
|
||||
user = userDao.getById(user.getId());
|
||||
user.setStorageCurrent(10_000L);
|
||||
userDao.updateQuota(user);
|
||||
|
||||
FileDeletedAsyncListener fileDeletedAsyncListener = new FileDeletedAsyncListener();
|
||||
TransactionUtil.commit();
|
||||
FileDeletedAsyncEvent event = new FileDeletedAsyncEvent();
|
||||
event.setFileSize(FILE_JPG_SIZE);
|
||||
event.setFileId(file.getId());
|
||||
event.setUserId(user.getId());
|
||||
fileDeletedAsyncListener.on(event);
|
||||
Assert.assertEquals(userDao.getById(user.getId()).getStorageCurrent(), Long.valueOf(10_000 - FILE_JPG_SIZE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateQuotaSizeUnknown() throws Exception {
|
||||
User user = createUser("updateQuotaSizeUnknown");
|
||||
File file = createFile(user, File.UNKNOWN_SIZE);
|
||||
UserDao userDao = new UserDao();
|
||||
user = userDao.getById(user.getId());
|
||||
user.setStorageCurrent(10_000L);
|
||||
userDao.updateQuota(user);
|
||||
|
||||
FileDeletedAsyncListener fileDeletedAsyncListener = new FileDeletedAsyncListener();
|
||||
TransactionUtil.commit();
|
||||
FileDeletedAsyncEvent event = new FileDeletedAsyncEvent();
|
||||
event.setFileSize(FILE_JPG_SIZE);
|
||||
event.setFileId(file.getId());
|
||||
event.setUserId(user.getId());
|
||||
fileDeletedAsyncListener.on(event);
|
||||
Assert.assertEquals(userDao.getById(user.getId()).getStorageCurrent(), Long.valueOf(10_000 - FILE_JPG_SIZE));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.sismics.docs.core.service;
|
||||
|
||||
import com.sismics.docs.BaseTransactionalTest;
|
||||
import com.sismics.docs.core.dao.FileDao;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestFileSizeService extends BaseTransactionalTest {
|
||||
|
||||
@Test
|
||||
public void processFileTest() throws Exception {
|
||||
User user = createUser("processFileTest");
|
||||
|
||||
FileDao fileDao = new FileDao();
|
||||
File file = createFile(user, File.UNKNOWN_SIZE);
|
||||
FileSizeService fileSizeService = new FileSizeService();
|
||||
fileSizeService.processFile(file);
|
||||
Assert.assertEquals(fileDao.getFile(file.getId()).getSize(), Long.valueOf(FILE_JPG_SIZE));
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.sismics.docs.core.util;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.sismics.BaseTest;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -14,7 +15,7 @@ import java.io.InputStream;
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class TestEncryptUtil {
|
||||
public class TestEncryptUtil extends BaseTest {
|
||||
@Test
|
||||
public void generatePrivateKeyTest() {
|
||||
String key = EncryptionUtil.generatePrivateKey();
|
||||
@@ -31,9 +32,9 @@ public class TestEncryptUtil {
|
||||
// NOP
|
||||
}
|
||||
Cipher cipher = EncryptionUtil.getEncryptionCipher("OnceUponATime");
|
||||
InputStream inputStream = new CipherInputStream(this.getClass().getResourceAsStream("/file/udhr.pdf"), cipher);
|
||||
InputStream inputStream = new CipherInputStream(getSystemResourceAsStream(FILE_PDF), cipher);
|
||||
byte[] encryptedData = ByteStreams.toByteArray(inputStream);
|
||||
byte[] assertData = ByteStreams.toByteArray(this.getClass().getResourceAsStream("/file/udhr_encrypted.pdf"));
|
||||
byte[] assertData = ByteStreams.toByteArray(getSystemResourceAsStream(FILE_PDF_ENCRYPTED));
|
||||
|
||||
Assert.assertEquals(encryptedData.length, assertData.length);
|
||||
}
|
||||
@@ -41,9 +42,9 @@ public class TestEncryptUtil {
|
||||
@Test
|
||||
public void decryptStreamTest() throws Exception {
|
||||
InputStream inputStream = EncryptionUtil.decryptInputStream(
|
||||
this.getClass().getResourceAsStream("/file/udhr_encrypted.pdf"), "OnceUponATime");
|
||||
getSystemResourceAsStream(FILE_PDF_ENCRYPTED), "OnceUponATime");
|
||||
byte[] encryptedData = ByteStreams.toByteArray(inputStream);
|
||||
byte[] assertData = ByteStreams.toByteArray(this.getClass().getResourceAsStream("/file/udhr.pdf"));
|
||||
byte[] assertData = ByteStreams.toByteArray(getSystemResourceAsStream(FILE_PDF));
|
||||
|
||||
Assert.assertEquals(encryptedData.length, assertData.length);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.sismics.docs.core.util;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.Resources;
|
||||
import com.sismics.BaseTest;
|
||||
import com.sismics.docs.core.dao.dto.DocumentDto;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.util.format.*;
|
||||
@@ -23,11 +24,11 @@ import java.util.Date;
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class TestFileUtil {
|
||||
public class TestFileUtil extends BaseTest {
|
||||
@Test
|
||||
public void extractContentOpenDocumentTextTest() throws Exception {
|
||||
Path path = Paths.get(ClassLoader.getSystemResource("file/document.odt").toURI());
|
||||
FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, "document.odt"));
|
||||
Path path = Paths.get(getResource(FILE_ODT).toURI());
|
||||
FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, FILE_ODT));
|
||||
Assert.assertNotNull(formatHandler);
|
||||
Assert.assertTrue(formatHandler instanceof OdtFormatHandler);
|
||||
String content = formatHandler.extractContent("eng", path);
|
||||
@@ -36,8 +37,8 @@ public class TestFileUtil {
|
||||
|
||||
@Test
|
||||
public void extractContentOfficeDocumentTest() throws Exception {
|
||||
Path path = Paths.get(ClassLoader.getSystemResource("file/document.docx").toURI());
|
||||
FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, "document.docx"));
|
||||
Path path = Paths.get(getResource(FILE_DOCX).toURI());
|
||||
FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, FILE_DOCX));
|
||||
Assert.assertNotNull(formatHandler);
|
||||
Assert.assertTrue(formatHandler instanceof DocxFormatHandler);
|
||||
String content = formatHandler.extractContent("eng", path);
|
||||
@@ -46,8 +47,8 @@ public class TestFileUtil {
|
||||
|
||||
@Test
|
||||
public void extractContentPowerpointTest() throws Exception {
|
||||
Path path = Paths.get(ClassLoader.getSystemResource("file/apache.pptx").toURI());
|
||||
FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, "apache.pptx"));
|
||||
Path path = Paths.get(getResource(FILE_PPTX).toURI());
|
||||
FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, FILE_PPTX));
|
||||
Assert.assertNotNull(formatHandler);
|
||||
Assert.assertTrue(formatHandler instanceof PptxFormatHandler);
|
||||
String content = formatHandler.extractContent("eng", path);
|
||||
@@ -56,8 +57,8 @@ public class TestFileUtil {
|
||||
|
||||
@Test
|
||||
public void extractContentPdf() throws Exception {
|
||||
Path path = Paths.get(ClassLoader.getSystemResource("file/udhr.pdf").toURI());
|
||||
FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, "udhr.pdf"));
|
||||
Path path = Paths.get(getResource(FILE_PDF).toURI());
|
||||
FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, FILE_PDF));
|
||||
Assert.assertNotNull(formatHandler);
|
||||
Assert.assertTrue(formatHandler instanceof PdfFormatHandler);
|
||||
String content = formatHandler.extractContent("eng", path);
|
||||
@@ -66,8 +67,8 @@ public class TestFileUtil {
|
||||
|
||||
@Test
|
||||
public void extractContentScannedPdf() throws Exception {
|
||||
Path path = Paths.get(ClassLoader.getSystemResource("file/scanned.pdf").toURI());
|
||||
FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, "scanned.pdf"));
|
||||
Path path = Paths.get(getResource("scanned.pdf").toURI());
|
||||
FormatHandler formatHandler = FormatHandlerUtil.find(MimeTypeUtil.guessMimeType(path, FILE_PDF_SCANNED));
|
||||
Assert.assertNotNull(formatHandler);
|
||||
Assert.assertTrue(formatHandler instanceof PdfFormatHandler);
|
||||
String content = formatHandler.extractContent("eng", path);
|
||||
@@ -76,12 +77,12 @@ public class TestFileUtil {
|
||||
|
||||
@Test
|
||||
public void convertToPdfTest() throws Exception {
|
||||
try (InputStream inputStream0 = Resources.getResource("file/apollo_landscape.jpg").openStream();
|
||||
InputStream inputStream1 = Resources.getResource("file/apollo_portrait.jpg").openStream();
|
||||
InputStream inputStream2 = Resources.getResource("file/udhr_encrypted.pdf").openStream();
|
||||
InputStream inputStream3 = Resources.getResource("file/document.docx").openStream();
|
||||
InputStream inputStream4 = Resources.getResource("file/document.odt").openStream();
|
||||
InputStream inputStream5 = Resources.getResource("file/apache.pptx").openStream()) {
|
||||
try (InputStream inputStream0 = getSystemResourceAsStream(FILE_JPG2);
|
||||
InputStream inputStream1 = getSystemResourceAsStream(FILE_JPG);
|
||||
InputStream inputStream2 = getSystemResourceAsStream(FILE_PDF_ENCRYPTED);
|
||||
InputStream inputStream3 = getSystemResourceAsStream(FILE_DOCX);
|
||||
InputStream inputStream4 = getSystemResourceAsStream(FILE_ODT);
|
||||
InputStream inputStream5 = getSystemResourceAsStream(FILE_PPTX)) {
|
||||
// Document
|
||||
DocumentDto documentDto = new DocumentDto();
|
||||
documentDto.setTitle("My super document 1");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.sismics.util;
|
||||
|
||||
import com.sismics.BaseTest;
|
||||
import com.sismics.util.mime.MimeType;
|
||||
import com.sismics.util.mime.MimeTypeUtil;
|
||||
import org.junit.Assert;
|
||||
@@ -13,59 +14,59 @@ import java.nio.file.Paths;
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class TestMimeTypeUtil {
|
||||
public class TestMimeTypeUtil extends BaseTest {
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
// Detect ODT files
|
||||
Path path = Paths.get(ClassLoader.getSystemResource("file/document.odt").toURI());
|
||||
Assert.assertEquals(MimeType.OPEN_DOCUMENT_TEXT, MimeTypeUtil.guessMimeType(path, "document.odt"));
|
||||
Path path = Paths.get(getResource(FILE_ODT).toURI());
|
||||
Assert.assertEquals(MimeType.OPEN_DOCUMENT_TEXT, MimeTypeUtil.guessMimeType(path, FILE_ODT));
|
||||
|
||||
// Detect DOCX files
|
||||
path = Paths.get(ClassLoader.getSystemResource("file/document.docx").toURI());
|
||||
Assert.assertEquals(MimeType.OFFICE_DOCUMENT, MimeTypeUtil.guessMimeType(path, "document.odt"));
|
||||
path = Paths.get(getResource(FILE_DOCX).toURI());
|
||||
Assert.assertEquals(MimeType.OFFICE_DOCUMENT, MimeTypeUtil.guessMimeType(path, FILE_ODT));
|
||||
|
||||
// Detect PPTX files
|
||||
path = Paths.get(ClassLoader.getSystemResource("file/apache.pptx").toURI());
|
||||
Assert.assertEquals(MimeType.OFFICE_PRESENTATION, MimeTypeUtil.guessMimeType(path, "apache.pptx"));
|
||||
path = Paths.get(getResource(FILE_PPTX).toURI());
|
||||
Assert.assertEquals(MimeType.OFFICE_PRESENTATION, MimeTypeUtil.guessMimeType(path, FILE_PPTX));
|
||||
|
||||
// Detect XLSX files
|
||||
path = Paths.get(ClassLoader.getSystemResource("file/document.xlsx").toURI());
|
||||
Assert.assertEquals(MimeType.OFFICE_SHEET, MimeTypeUtil.guessMimeType(path, "document.xlsx"));
|
||||
path = Paths.get(getResource(FILE_XLSX).toURI());
|
||||
Assert.assertEquals(MimeType.OFFICE_SHEET, MimeTypeUtil.guessMimeType(path, FILE_XLSX));
|
||||
|
||||
// Detect TXT files
|
||||
path = Paths.get(ClassLoader.getSystemResource("file/document.txt").toURI());
|
||||
Assert.assertEquals(MimeType.TEXT_PLAIN, MimeTypeUtil.guessMimeType(path, "document.txt"));
|
||||
path = Paths.get(getResource(FILE_TXT).toURI());
|
||||
Assert.assertEquals(MimeType.TEXT_PLAIN, MimeTypeUtil.guessMimeType(path, FILE_TXT));
|
||||
|
||||
// Detect CSV files
|
||||
path = Paths.get(ClassLoader.getSystemResource("file/document.csv").toURI());
|
||||
Assert.assertEquals(MimeType.TEXT_CSV, MimeTypeUtil.guessMimeType(path, "document.csv"));
|
||||
path = Paths.get(getResource(FILE_CSV).toURI());
|
||||
Assert.assertEquals(MimeType.TEXT_CSV, MimeTypeUtil.guessMimeType(path, FILE_CSV));
|
||||
|
||||
// Detect PDF files
|
||||
path = Paths.get(ClassLoader.getSystemResource("file/udhr.pdf").toURI());
|
||||
Assert.assertEquals(MimeType.APPLICATION_PDF, MimeTypeUtil.guessMimeType(path, "udhr.pdf"));
|
||||
path = Paths.get(getResource(FILE_PDF).toURI());
|
||||
Assert.assertEquals(MimeType.APPLICATION_PDF, MimeTypeUtil.guessMimeType(path, FILE_PDF));
|
||||
|
||||
// Detect JPEG files
|
||||
path = Paths.get(ClassLoader.getSystemResource("file/apollo_portrait.jpg").toURI());
|
||||
Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(path, "apollo_portrait.jpg"));
|
||||
path = Paths.get(getResource(FILE_JPG).toURI());
|
||||
Assert.assertEquals(MimeType.IMAGE_JPEG, MimeTypeUtil.guessMimeType(path, FILE_JPG));
|
||||
|
||||
// Detect GIF files
|
||||
path = Paths.get(ClassLoader.getSystemResource("file/image.gif").toURI());
|
||||
Assert.assertEquals(MimeType.IMAGE_GIF, MimeTypeUtil.guessMimeType(path, "image.gif"));
|
||||
path = Paths.get(getResource(FILE_GIF).toURI());
|
||||
Assert.assertEquals(MimeType.IMAGE_GIF, MimeTypeUtil.guessMimeType(path, FILE_GIF));
|
||||
|
||||
// Detect PNG files
|
||||
path = Paths.get(ClassLoader.getSystemResource("file/image.png").toURI());
|
||||
Assert.assertEquals(MimeType.IMAGE_PNG, MimeTypeUtil.guessMimeType(path, "image.png"));
|
||||
path = Paths.get(getResource(FILE_PNG).toURI());
|
||||
Assert.assertEquals(MimeType.IMAGE_PNG, MimeTypeUtil.guessMimeType(path, FILE_PNG));
|
||||
|
||||
// Detect ZIP files
|
||||
path = Paths.get(ClassLoader.getSystemResource("file/document.zip").toURI());
|
||||
Assert.assertEquals(MimeType.APPLICATION_ZIP, MimeTypeUtil.guessMimeType(path, "document.zip"));
|
||||
path = Paths.get(getResource(FILE_ZIP).toURI());
|
||||
Assert.assertEquals(MimeType.APPLICATION_ZIP, MimeTypeUtil.guessMimeType(path, FILE_ZIP));
|
||||
|
||||
// Detect WEBM files
|
||||
path = Paths.get(ClassLoader.getSystemResource("file/video.webm").toURI());
|
||||
Assert.assertEquals(MimeType.VIDEO_WEBM, MimeTypeUtil.guessMimeType(path, "video.webm"));
|
||||
path = Paths.get(getResource(FILE_WEBM).toURI());
|
||||
Assert.assertEquals(MimeType.VIDEO_WEBM, MimeTypeUtil.guessMimeType(path, FILE_WEBM));
|
||||
|
||||
// Detect MP4 files
|
||||
path = Paths.get(ClassLoader.getSystemResource("file/video.mp4").toURI());
|
||||
Assert.assertEquals(MimeType.VIDEO_MP4, MimeTypeUtil.guessMimeType(path, "video.mp4"));
|
||||
path = Paths.get(getResource(FILE_MP4).toURI());
|
||||
Assert.assertEquals(MimeType.VIDEO_MP4, MimeTypeUtil.guessMimeType(path, FILE_MP4));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.sismics.util.format;
|
||||
|
||||
import com.sismics.BaseTest;
|
||||
import com.sismics.docs.core.util.format.PdfFormatHandler;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
@@ -11,14 +12,14 @@ import java.nio.file.Paths;
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class TestPdfFormatHandler {
|
||||
public class TestPdfFormatHandler extends BaseTest {
|
||||
/**
|
||||
* Test related to https://github.com/sismics/docs/issues/373.
|
||||
*/
|
||||
@Test
|
||||
public void testIssue373() throws Exception {
|
||||
PdfFormatHandler formatHandler = new PdfFormatHandler();
|
||||
String content = formatHandler.extractContent("deu", Paths.get(ClassLoader.getSystemResource("file/issue373.pdf").toURI()));
|
||||
String content = formatHandler.extractContent("deu", Paths.get(getResource("issue373.pdf").toURI()));
|
||||
Assert.assertTrue(content.contains("Aufrechterhaltung"));
|
||||
Assert.assertTrue(content.contains("Außentemperatur"));
|
||||
Assert.assertTrue(content.contains("Grundumsatzmessungen"));
|
||||
|
||||
10
docs-core/src/test/resources/log4j.properties
Normal file
10
docs-core/src/test/resources/log4j.properties
Normal file
@@ -0,0 +1,10 @@
|
||||
log4j.rootCategory=DEBUG, CONSOLE, MEMORY
|
||||
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.CONSOLE.layout.ConversionPattern=%d{DATE} %p %l %m %n
|
||||
log4j.appender.MEMORY=com.sismics.util.log4j.MemoryAppender
|
||||
log4j.appender.MEMORY.size=1000
|
||||
|
||||
log4j.logger.com.sismics=INFO
|
||||
log4j.logger.org.hibernate=ERROR
|
||||
log4j.logger.org.apache.directory=ERROR
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%date [%t] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<appender name="MEMORY" class="com.sismics.util.logback.MemoryAppender">
|
||||
<size>1000</size>
|
||||
</appender>
|
||||
<logger name="org.apache.directory" level="ERROR"/>
|
||||
<logger name="com.sismics" level="INFO"/>
|
||||
<logger name="org.hibernate" level="ERROR"/>
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="MEMORY"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -48,6 +48,11 @@
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
@@ -59,6 +64,17 @@
|
||||
<artifactId>joda-time</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jul-to-slf4j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
<version>4.4.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.sismics.util.JsonUtil;
|
||||
|
||||
import jakarta.json.Json;
|
||||
import jakarta.json.JsonObjectBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
@@ -18,12 +19,15 @@ import java.nio.file.Files;
|
||||
*/
|
||||
public class RestUtil {
|
||||
/**
|
||||
* Transform a File into its JSON representation
|
||||
* Transform a File into its JSON representation.
|
||||
* If the file size it is not stored in the database the size can be wrong
|
||||
* because the encrypted file size is used.
|
||||
* @param fileDb a file
|
||||
* @return the JSON
|
||||
*/
|
||||
public static JsonObjectBuilder fileToJsonObjectBuilder(File fileDb) {
|
||||
try {
|
||||
long fileSize = fileDb.getSize().equals(File.UNKNOWN_SIZE) ? Files.size(DirectoryUtil.getStorageDirectory().resolve(fileDb.getId())) : fileDb.getSize();
|
||||
return Json.createObjectBuilder()
|
||||
.add("id", fileDb.getId())
|
||||
.add("processing", FileUtil.isProcessingFile(fileDb.getId()))
|
||||
@@ -32,7 +36,7 @@ public class RestUtil {
|
||||
.add("mimetype", fileDb.getMimeType())
|
||||
.add("document_id", JsonUtil.nullable(fileDb.getDocumentId()))
|
||||
.add("create_date", fileDb.getCreateDate().getTime())
|
||||
.add("size", Files.size(DirectoryUtil.getStorageDirectory().resolve(fileDb.getId())));
|
||||
.add("size", fileSize);
|
||||
} catch (IOException e) {
|
||||
throw new ServerException("FileError", "Unable to get the size of " + fileDb.getId(), e);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public class ValidationUtil {
|
||||
|
||||
private static Pattern ALPHANUMERIC_PATTERN = Pattern.compile("[a-zA-Z0-9_]+");
|
||||
|
||||
private static Pattern USERNAME_PATTERN = Pattern.compile("[a-zA-Z0-9_@\\.]+");
|
||||
private static Pattern USERNAME_PATTERN = Pattern.compile("[a-zA-Z0-9_@.-]+");
|
||||
|
||||
/**
|
||||
* Checks that the argument is not null.
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package com.sismics.util.filter;
|
||||
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.exceptions.JWTVerificationException;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import com.auth0.jwt.interfaces.JWTVerifier;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.util.Base64;
|
||||
|
||||
import com.sismics.docs.core.constant.Constants;
|
||||
import com.sismics.docs.core.dao.UserDao;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import jakarta.json.Json;
|
||||
import jakarta.json.JsonArray;
|
||||
import jakarta.json.JsonObject;
|
||||
import jakarta.json.JsonReader;
|
||||
import jakarta.servlet.FilterConfig;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import static java.util.Optional.ofNullable;
|
||||
|
||||
/**
|
||||
* This filter is used to authenticate the user having an active session by validating a jwt token.
|
||||
* The filter extracts the jwt token stored from Authorization header.
|
||||
* It validates the token by calling an Identity Broker like KeyCloak.
|
||||
* If validated, the user is retrieved, and the filter injects a UserPrincipal into the request attribute.
|
||||
*
|
||||
* @author smitra
|
||||
*/
|
||||
public class JwtBasedSecurityFilter extends SecurityFilter {
|
||||
private static final Logger log = LoggerFactory.getLogger(JwtBasedSecurityFilter.class);
|
||||
private static final okhttp3.OkHttpClient client = new okhttp3.OkHttpClient();
|
||||
/**
|
||||
* Name of the header used to store the authentication token.
|
||||
*/
|
||||
public static final String HEADER_NAME = "Authorization";
|
||||
/**
|
||||
* True if this authentication method is enabled.
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) {
|
||||
enabled = Boolean.parseBoolean(filterConfig.getInitParameter("enabled"))
|
||||
|| Boolean.parseBoolean(System.getProperty("docs.jwt_authentication"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected User authenticate(final HttpServletRequest request) {
|
||||
if (!enabled) {
|
||||
return null;
|
||||
}
|
||||
log.info("Jwt authentication started");
|
||||
User user = null;
|
||||
String token = extractAuthToken(request).replace("Bearer ", "");
|
||||
DecodedJWT jwt = JWT.decode(token);
|
||||
if (verifyJwt(jwt, token)) {
|
||||
String email = jwt.getClaim("preferred_username").toString();
|
||||
UserDao userDao = new UserDao();
|
||||
user = userDao.getActiveByUsername(email);
|
||||
if (user == null) {
|
||||
user = new User();
|
||||
user.setRoleId(Constants.DEFAULT_USER_ROLE);
|
||||
user.setUsername(email);
|
||||
user.setEmail(email);
|
||||
user.setStorageQuota(Long.parseLong(ofNullable(System.getenv(Constants.GLOBAL_QUOTA_ENV))
|
||||
.orElse("1073741824")));
|
||||
user.setPassword(UUID.randomUUID().toString());
|
||||
try {
|
||||
userDao.create(user, email);
|
||||
log.info("user created");
|
||||
} catch (Exception e) {
|
||||
log.info("Error:" + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
private boolean verifyJwt(final DecodedJWT jwt, final String token) {
|
||||
|
||||
try {
|
||||
buildJWTVerifier(jwt).verify(token);
|
||||
// if token is valid no exception will be thrown
|
||||
log.info("Valid TOKEN");
|
||||
return Boolean.TRUE;
|
||||
} catch (CertificateException e) {
|
||||
//if CertificateException comes from buildJWTVerifier()
|
||||
log.info("InValid TOKEN: " + e.getMessage());
|
||||
return Boolean.FALSE;
|
||||
} catch (JWTVerificationException e) {
|
||||
// if JWT Token in invalid
|
||||
log.info("InValid TOKEN: " + e.getMessage() );
|
||||
return Boolean.FALSE;
|
||||
} catch (Exception e) {
|
||||
// If any other exception comes
|
||||
log.info("InValid TOKEN, Exception Occurred: " + e.getMessage());
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
private String extractAuthToken(final HttpServletRequest request) {
|
||||
return ofNullable(request.getHeader("Authorization")).orElse("");
|
||||
}
|
||||
|
||||
private RSAPublicKey getPublicKey(DecodedJWT jwt) {
|
||||
String jwtIssuerCerts = jwt.getIssuer() + "/protocol/openid-connect/certs";
|
||||
String publicKey = "";
|
||||
RSAPublicKey rsaPublicKey = null;
|
||||
Request request = new Request.Builder()
|
||||
.url(jwtIssuerCerts)
|
||||
.get()
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
log.info("Successfully called the jwt issuer at: " + jwtIssuerCerts + " - " + response.code());
|
||||
assert response.body() != null;
|
||||
if (response.isSuccessful()) {
|
||||
try (Reader reader = response.body().charStream()) {
|
||||
try (JsonReader jsonReader = Json.createReader(reader)) {
|
||||
JsonObject jwks = jsonReader.readObject();
|
||||
JsonArray keys = jwks.getJsonArray("keys");
|
||||
publicKey = keys.stream().filter(key -> Objects.equals(key.asJsonObject().getString("kid"),
|
||||
jwt.getKeyId()))
|
||||
.findFirst()
|
||||
.map(k -> k.asJsonObject().getJsonArray("x5c").getString(0))
|
||||
.orElse("");
|
||||
var decode = Base64.getDecoder().decode(publicKey);
|
||||
var certificate = CertificateFactory.getInstance("X.509")
|
||||
.generateCertificate(new ByteArrayInputStream(decode));
|
||||
rsaPublicKey = (RSAPublicKey) certificate.getPublicKey();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Error calling the jwt issuer at: " + jwtIssuerCerts, e);
|
||||
} catch (CertificateException e) {
|
||||
log.error("Error in getting the certificate: ", e);
|
||||
}
|
||||
return rsaPublicKey;
|
||||
}
|
||||
|
||||
private JWTVerifier buildJWTVerifier(DecodedJWT jwt) throws CertificateException {
|
||||
var algo = Algorithm.RSA256(getPublicKey(jwt), null);
|
||||
return JWT.require(algo).build();
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,23 @@
|
||||
package com.sismics.util.filter;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.rolling.RollingFileAppender;
|
||||
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
|
||||
import ch.qos.logback.core.util.FileSize;
|
||||
import com.sismics.docs.core.model.context.AppContext;
|
||||
import com.sismics.docs.core.util.DirectoryUtil;
|
||||
import com.sismics.docs.core.util.TransactionUtil;
|
||||
import com.sismics.util.EnvironmentUtil;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
import com.sismics.util.jpa.EMF;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityTransaction;
|
||||
import jakarta.servlet.*;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
import org.apache.log4j.Level;
|
||||
import org.apache.log4j.PatternLayout;
|
||||
import org.apache.log4j.RollingFileAppender;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.bridge.SLF4JBridgeHandler;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityTransaction;
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
|
||||
@@ -51,30 +48,18 @@ public class RequestContextFilter implements Filter {
|
||||
|
||||
|
||||
// Initialize file logger
|
||||
LoggerContext logContext = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<>();
|
||||
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
|
||||
encoder.setContext(logContext);
|
||||
encoder.setPattern("%date [%t] %-5level %logger{36} - %msg%n");
|
||||
encoder.start();
|
||||
SizeAndTimeBasedRollingPolicy<ILoggingEvent> rollingPolicy = new SizeAndTimeBasedRollingPolicy<>();
|
||||
rollingPolicy.setMaxFileSize(FileSize.valueOf("5MB"));
|
||||
rollingPolicy.setFileNamePattern("docs.%d{yyyy-MM-dd_HH}.log");
|
||||
rollingPolicy.setMaxHistory(5);
|
||||
rollingPolicy.setContext(logContext);
|
||||
rollingPolicy.setParent(appender);
|
||||
rollingPolicy.start();
|
||||
appender.setContext(logContext);
|
||||
appender.setName("FILE");
|
||||
appender.setFile(DirectoryUtil.getLogDirectory().resolve("docs.log").toString());
|
||||
appender.setEncoder(encoder);
|
||||
appender.setRollingPolicy(rollingPolicy);
|
||||
appender.setAppend(true);
|
||||
appender.start();
|
||||
ch.qos.logback.classic.Logger logger = logContext.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
logger.setAdditive(false);
|
||||
logger.setLevel(Level.INFO);
|
||||
logger.addAppender(appender);
|
||||
RollingFileAppender fileAppender = new RollingFileAppender();
|
||||
fileAppender.setName("FILE");
|
||||
fileAppender.setFile(DirectoryUtil.getLogDirectory().resolve("docs.log").toString());
|
||||
fileAppender.setLayout(new PatternLayout("%d{DATE} %p %l %m %n"));
|
||||
fileAppender.setThreshold(Level.INFO);
|
||||
fileAppender.setAppend(true);
|
||||
fileAppender.setMaxFileSize("5MB");
|
||||
fileAppender.setMaxBackupIndex(5);
|
||||
fileAppender.activateOptions();
|
||||
org.apache.log4j.Logger.getRootLogger().addAppender(fileAppender);
|
||||
SLF4JBridgeHandler.removeHandlersForRootLogger();
|
||||
SLF4JBridgeHandler.install();
|
||||
|
||||
// Initialize the application context
|
||||
TransactionUtil.handle(AppContext::getInstance);
|
||||
|
||||
@@ -25,6 +25,9 @@ import jakarta.ws.rs.core.UriBuilder;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -39,7 +42,9 @@ public abstract class BaseJerseyTest extends JerseyTest {
|
||||
protected static final String FILE_DOCUMENT_ODT = "file/document.odt";
|
||||
protected static final String FILE_DOCUMENT_TXT = "file/document.txt";
|
||||
protected static final String FILE_EINSTEIN_ROOSEVELT_LETTER_PNG = "file/Einstein-Roosevelt-letter.png";
|
||||
protected static final long FILE_EINSTEIN_ROOSEVELT_LETTER_PNG_SIZE = 292641L;
|
||||
protected static final String FILE_PIA_00452_JPG = "file/PIA00452.jpg";
|
||||
protected static final long FILE_PIA_00452_JPG_SIZE = 163510L;
|
||||
protected static final String FILE_VIDEO_WEBM = "file/video.webm";
|
||||
protected static final String FILE_WIKIPEDIA_PDF = "file/wikipedia.pdf";
|
||||
protected static final String FILE_WIKIPEDIA_ZIP = "file/wikipedia.zip";
|
||||
@@ -58,7 +63,11 @@ public abstract class BaseJerseyTest extends JerseyTest {
|
||||
* Test mail server.
|
||||
*/
|
||||
private Wiser wiser;
|
||||
|
||||
|
||||
public String adminToken() {
|
||||
return clientUtil.login("admin", "admin", false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
|
||||
return new ExternalTestContainerFactory();
|
||||
|
||||
@@ -63,6 +63,11 @@
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>log4j</groupId>
|
||||
<artifactId>log4j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
@@ -124,6 +129,12 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.directory.server</groupId>
|
||||
<artifactId>apacheds-all</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -177,7 +188,7 @@
|
||||
</systemProperties>
|
||||
<webApp>
|
||||
<contextPath>/docs-web</contextPath>
|
||||
<overrideDescriptor>src/dev/main/webapp/web-override.xml</overrideDescriptor>
|
||||
<overrideDescriptor>${project.basedir}/src/dev/main/webapp/web-override.xml</overrideDescriptor>
|
||||
</webApp>
|
||||
</configuration>
|
||||
</plugin>
|
||||
@@ -255,8 +266,8 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
<configuration>
|
||||
<warSourceDirectory>${basedir}/src/main/webapp/dist</warSourceDirectory>
|
||||
<webXml>src\main\webapp\WEB-INF\web.xml</webXml>
|
||||
<warSourceDirectory>${project.basedir}/src/main/webapp/dist</warSourceDirectory>
|
||||
<webXml>src/main/webapp/WEB-INF/web.xml</webXml>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=28
|
||||
db.version=31
|
||||
|
||||
11
docs-web/src/dev/resources/log4j.properties
Normal file
11
docs-web/src/dev/resources/log4j.properties
Normal file
@@ -0,0 +1,11 @@
|
||||
log4j.rootCategory=WARN, CONSOLE, MEMORY
|
||||
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.CONSOLE.layout.ConversionPattern=%d{DATE} %p %l %m %n
|
||||
log4j.appender.MEMORY=com.sismics.util.log4j.MemoryAppender
|
||||
log4j.appender.MEMORY.size=1000
|
||||
|
||||
log4j.logger.com.sismics=DEBUG
|
||||
log4j.logger.org.apache.pdfbox=ERROR
|
||||
log4j.logger.org.glassfish.jersey.servlet.WebComponent=ERROR
|
||||
log4j.logger.org.apache.directory=ERROR
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%date [%t] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<appender name="MEMORY" class="com.sismics.util.logback.MemoryAppender">
|
||||
<size>1000</size>
|
||||
</appender>
|
||||
<logger name="org.apache.directory" level="ERROR"/>
|
||||
<logger name="com.sismics" level="DEBUG"/>
|
||||
<logger name="org.apache.pdfbox" level="ERROR"/>
|
||||
<logger name="org.glassfish.jersey.servlet.WebComponent" level="ERROR"/>
|
||||
<root level="WARN">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="MEMORY"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.sismics.docs.rest.resource;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import com.google.common.base.Strings;
|
||||
import com.sismics.docs.core.constant.ConfigType;
|
||||
import com.sismics.docs.core.constant.Constants;
|
||||
@@ -24,9 +23,9 @@ import com.sismics.rest.exception.ServerException;
|
||||
import com.sismics.rest.util.ValidationUtil;
|
||||
import com.sismics.util.JsonUtil;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
import com.sismics.util.logback.LogCriteria;
|
||||
import com.sismics.util.logback.LogEntry;
|
||||
import com.sismics.util.logback.MemoryAppender;
|
||||
import com.sismics.util.log4j.LogCriteria;
|
||||
import com.sismics.util.log4j.LogEntry;
|
||||
import com.sismics.util.log4j.MemoryAppender;
|
||||
import jakarta.json.Json;
|
||||
import jakarta.json.JsonArrayBuilder;
|
||||
import jakarta.json.JsonObjectBuilder;
|
||||
@@ -35,6 +34,8 @@ import jakarta.persistence.Query;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.log4j.Appender;
|
||||
import org.apache.log4j.Level;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -46,7 +47,7 @@ import java.util.*;
|
||||
|
||||
/**
|
||||
* General app REST resource.
|
||||
*
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
@Path("/app")
|
||||
@@ -55,11 +56,11 @@ public class AppResource extends BaseResource {
|
||||
* Logger.
|
||||
*/
|
||||
private static final Logger log = LoggerFactory.getLogger(AppResource.class);
|
||||
|
||||
|
||||
/**
|
||||
* Returns informations about the application.
|
||||
* Returns information about the application.
|
||||
*
|
||||
* @api {get} /app Get application informations
|
||||
* @api {get} /app Get application information
|
||||
* @apiName GetApp
|
||||
* @apiGroup App
|
||||
* @apiSuccess {String} current_version API current version
|
||||
@@ -84,6 +85,7 @@ public class AppResource extends BaseResource {
|
||||
String currentVersion = configBundle.getString("api.current_version");
|
||||
String minVersion = configBundle.getString("api.min_version");
|
||||
Boolean guestLogin = ConfigUtil.getConfigBooleanValue(ConfigType.GUEST_LOGIN);
|
||||
Boolean ocrEnabled = ConfigUtil.getConfigBooleanValue(ConfigType.OCR_ENABLED, true);
|
||||
String defaultLanguage = ConfigUtil.getConfigStringValue(ConfigType.DEFAULT_LANGUAGE);
|
||||
UserDao userDao = new UserDao();
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
@@ -97,6 +99,7 @@ public class AppResource extends BaseResource {
|
||||
.add("current_version", currentVersion.replace("-SNAPSHOT", ""))
|
||||
.add("min_version", minVersion)
|
||||
.add("guest_login", guestLogin)
|
||||
.add("ocr_enabled", ocrEnabled)
|
||||
.add("default_language", defaultLanguage)
|
||||
.add("queued_tasks", AppContext.getInstance().getQueuedTaskCount())
|
||||
.add("total_memory", Runtime.getRuntime().totalMemory())
|
||||
@@ -139,6 +142,34 @@ public class AppResource extends BaseResource {
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable OCR.
|
||||
*
|
||||
* @api {post} /app/ocr Enable/disable OCR
|
||||
* @apiName PostAppOcr
|
||||
* @apiGroup App
|
||||
* @apiParam {Boolean} enabled If true, enable OCR
|
||||
* @apiError (client) ForbiddenError Access denied
|
||||
* @apiPermission admin
|
||||
* @apiVersion 1.5.0
|
||||
*
|
||||
* @param enabled If true, enable OCR
|
||||
* @return Response
|
||||
*/
|
||||
@POST
|
||||
@Path("ocr")
|
||||
public Response ocr(@FormParam("enabled") Boolean enabled) {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
checkBaseFunction(BaseFunction.ADMIN);
|
||||
|
||||
ConfigDao configDao = new ConfigDao();
|
||||
configDao.update(ConfigType.OCR_ENABLED, enabled.toString());
|
||||
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* General application configuration.
|
||||
*
|
||||
@@ -239,7 +270,7 @@ public class AppResource extends BaseResource {
|
||||
|
||||
return Response.ok().entity(response.build()).build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configure the SMTP server.
|
||||
*
|
||||
@@ -538,14 +569,22 @@ public class AppResource extends BaseResource {
|
||||
}
|
||||
checkBaseFunction(BaseFunction.ADMIN);
|
||||
|
||||
// Get the memory appender
|
||||
org.apache.log4j.Logger logger = org.apache.log4j.Logger.getRootLogger();
|
||||
Appender appender = logger.getAppender("MEMORY");
|
||||
if (!(appender instanceof MemoryAppender)) {
|
||||
throw new ServerException("ServerError", "MEMORY appender not configured");
|
||||
}
|
||||
MemoryAppender memoryAppender = (MemoryAppender) appender;
|
||||
|
||||
// Find the logs
|
||||
LogCriteria logCriteria = new LogCriteria()
|
||||
.setMinLevel(Level.toLevel(StringUtils.stripToNull(minLevel)))
|
||||
.setTag(StringUtils.stripToNull(tag))
|
||||
.setMessage(StringUtils.stripToNull(message));
|
||||
|
||||
|
||||
PaginatedList<LogEntry> paginatedList = PaginatedLists.create(limit, offset);
|
||||
MemoryAppender.find(logCriteria, paginatedList);
|
||||
memoryAppender.find(logCriteria, paginatedList);
|
||||
JsonArrayBuilder logs = Json.createArrayBuilder();
|
||||
for (LogEntry logEntry : paginatedList.getResultList()) {
|
||||
logs.add(Json.createObjectBuilder()
|
||||
@@ -554,14 +593,14 @@ public class AppResource extends BaseResource {
|
||||
.add("tag", logEntry.getTag())
|
||||
.add("message", logEntry.getMessage()));
|
||||
}
|
||||
|
||||
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
.add("total", paginatedList.getResultCount())
|
||||
.add("logs", logs);
|
||||
|
||||
|
||||
return Response.ok().entity(response.build()).build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Destroy and rebuild the search index.
|
||||
*
|
||||
@@ -583,7 +622,7 @@ public class AppResource extends BaseResource {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
checkBaseFunction(BaseFunction.ADMIN);
|
||||
|
||||
|
||||
RebuildIndexAsyncEvent rebuildIndexAsyncEvent = new RebuildIndexAsyncEvent();
|
||||
ThreadLocalContext.get().addAsyncEvent(rebuildIndexAsyncEvent);
|
||||
|
||||
@@ -592,7 +631,7 @@ public class AppResource extends BaseResource {
|
||||
.add("status", "ok");
|
||||
return Response.ok().entity(response.build()).build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clean storage.
|
||||
*
|
||||
@@ -614,7 +653,7 @@ public class AppResource extends BaseResource {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
checkBaseFunction(BaseFunction.ADMIN);
|
||||
|
||||
|
||||
// Get all files
|
||||
FileDao fileDao = new FileDao();
|
||||
List<File> fileList = fileDao.findAll(0, Integer.MAX_VALUE);
|
||||
@@ -623,7 +662,7 @@ public class AppResource extends BaseResource {
|
||||
fileMap.put(file.getId(), file);
|
||||
}
|
||||
log.info("Checking {} files", fileMap.size());
|
||||
|
||||
|
||||
// Check if each stored file is valid
|
||||
try (DirectoryStream<java.nio.file.Path> storedFileList = Files.newDirectoryStream(DirectoryUtil.getStorageDirectory())) {
|
||||
for (java.nio.file.Path storedFile : storedFileList) {
|
||||
@@ -637,7 +676,7 @@ public class AppResource extends BaseResource {
|
||||
} catch (IOException e) {
|
||||
throw new ServerException("FileError", "Error deleting orphan files", e);
|
||||
}
|
||||
|
||||
|
||||
// Hard delete orphan audit logs
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
StringBuilder sb = new StringBuilder("delete from T_AUDIT_LOG al where al.LOG_ID_C in (select al.LOG_ID_C from T_AUDIT_LOG al ");
|
||||
@@ -651,7 +690,7 @@ public class AppResource extends BaseResource {
|
||||
sb.append(" where d.DOC_ID_C is null and a.ACL_ID_C is null and c.COM_ID_C is null and f.FIL_ID_C is null and t.TAG_ID_C is null and u.USE_ID_C is null and g.GRP_ID_C is null)");
|
||||
Query q = em.createNativeQuery(sb.toString());
|
||||
log.info("Deleting {} orphan audit logs", q.executeUpdate());
|
||||
|
||||
|
||||
// Soft delete orphan ACLs
|
||||
sb = new StringBuilder("update T_ACL a set ACL_DELETEDATE_D = :dateNow where a.ACL_ID_C in (select a.ACL_ID_C from T_ACL a ");
|
||||
sb.append(" left join T_SHARE s on s.SHA_ID_C = a.ACL_TARGETID_C ");
|
||||
@@ -663,37 +702,37 @@ public class AppResource extends BaseResource {
|
||||
q = em.createNativeQuery(sb.toString());
|
||||
q.setParameter("dateNow", new Date());
|
||||
log.info("Deleting {} orphan ACLs", q.executeUpdate());
|
||||
|
||||
|
||||
// Soft delete orphan comments
|
||||
q = em.createNativeQuery("update T_COMMENT set COM_DELETEDATE_D = :dateNow where COM_ID_C in (select c.COM_ID_C from T_COMMENT c left join T_DOCUMENT d on d.DOC_ID_C = c.COM_IDDOC_C and d.DOC_DELETEDATE_D is null where d.DOC_ID_C is null)");
|
||||
q.setParameter("dateNow", new Date());
|
||||
log.info("Deleting {} orphan comments", q.executeUpdate());
|
||||
|
||||
|
||||
// Soft delete orphan document tag links
|
||||
q = em.createNativeQuery("update T_DOCUMENT_TAG set DOT_DELETEDATE_D = :dateNow where DOT_ID_C in (select dt.DOT_ID_C from T_DOCUMENT_TAG dt left join T_DOCUMENT d on dt.DOT_IDDOCUMENT_C = d.DOC_ID_C and d.DOC_DELETEDATE_D is null left join T_TAG t on t.TAG_ID_C = dt.DOT_IDTAG_C and t.TAG_DELETEDATE_D is null where d.DOC_ID_C is null or t.TAG_ID_C is null)");
|
||||
q.setParameter("dateNow", new Date());
|
||||
log.info("Deleting {} orphan document tag links", q.executeUpdate());
|
||||
|
||||
|
||||
// Soft delete orphan shares
|
||||
q = em.createNativeQuery("update T_SHARE set SHA_DELETEDATE_D = :dateNow where SHA_ID_C in (select s.SHA_ID_C from T_SHARE s left join T_ACL a on a.ACL_TARGETID_C = s.SHA_ID_C and a.ACL_DELETEDATE_D is null where a.ACL_ID_C is null)");
|
||||
q.setParameter("dateNow", new Date());
|
||||
log.info("Deleting {} orphan shares", q.executeUpdate());
|
||||
|
||||
|
||||
// Soft delete orphan tags
|
||||
q = em.createNativeQuery("update T_TAG set TAG_DELETEDATE_D = :dateNow where TAG_ID_C in (select t.TAG_ID_C from T_TAG t left join T_USER u on u.USE_ID_C = t.TAG_IDUSER_C and u.USE_DELETEDATE_D is null where u.USE_ID_C is null)");
|
||||
q.setParameter("dateNow", new Date());
|
||||
log.info("Deleting {} orphan tags", q.executeUpdate());
|
||||
|
||||
|
||||
// Soft delete orphan documents
|
||||
q = em.createNativeQuery("update T_DOCUMENT set DOC_DELETEDATE_D = :dateNow where DOC_ID_C in (select d.DOC_ID_C from T_DOCUMENT d left join T_USER u on u.USE_ID_C = d.DOC_IDUSER_C and u.USE_DELETEDATE_D is null where u.USE_ID_C is null)");
|
||||
q.setParameter("dateNow", new Date());
|
||||
log.info("Deleting {} orphan documents", q.executeUpdate());
|
||||
|
||||
|
||||
// Soft delete orphan files
|
||||
q = em.createNativeQuery("update T_FILE set FIL_DELETEDATE_D = :dateNow where FIL_ID_C in (select f.FIL_ID_C from T_FILE f left join T_USER u on u.USE_ID_C = f.FIL_IDUSER_C and u.USE_DELETEDATE_D is null where u.USE_ID_C is null)");
|
||||
q.setParameter("dateNow", new Date());
|
||||
log.info("Deleting {} orphan files", q.executeUpdate());
|
||||
|
||||
|
||||
// Hard delete softly deleted data
|
||||
log.info("Deleting {} soft deleted document tag links", em.createQuery("delete DocumentTag where deleteDate is not null").executeUpdate());
|
||||
log.info("Deleting {} soft deleted ACLs", em.createQuery("delete Acl where deleteDate is not null").executeUpdate());
|
||||
@@ -704,7 +743,7 @@ public class AppResource extends BaseResource {
|
||||
log.info("Deleting {} soft deleted documents", em.createQuery("delete Document where deleteDate is not null").executeUpdate());
|
||||
log.info("Deleting {} soft deleted users", em.createQuery("delete User where deleteDate is not null").executeUpdate());
|
||||
log.info("Deleting {} soft deleted groups", em.createQuery("delete Group where deleteDate is not null").executeUpdate());
|
||||
|
||||
|
||||
// Always return OK
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
.add("status", "ok");
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
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;
|
||||
import com.sismics.docs.core.constant.Constants;
|
||||
import com.sismics.docs.core.constant.PermType;
|
||||
import com.sismics.docs.core.dao.*;
|
||||
import com.sismics.docs.core.dao.AclDao;
|
||||
import com.sismics.docs.core.dao.ContributorDao;
|
||||
import com.sismics.docs.core.dao.DocumentDao;
|
||||
import com.sismics.docs.core.dao.FileDao;
|
||||
import com.sismics.docs.core.dao.RelationDao;
|
||||
import com.sismics.docs.core.dao.RouteStepDao;
|
||||
import com.sismics.docs.core.dao.TagDao;
|
||||
import com.sismics.docs.core.dao.UserDao;
|
||||
import com.sismics.docs.core.dao.criteria.DocumentCriteria;
|
||||
import com.sismics.docs.core.dao.criteria.TagCriteria;
|
||||
import com.sismics.docs.core.dao.dto.*;
|
||||
import com.sismics.docs.core.dao.dto.AclDto;
|
||||
import com.sismics.docs.core.dao.dto.ContributorDto;
|
||||
import com.sismics.docs.core.dao.dto.DocumentDto;
|
||||
import com.sismics.docs.core.dao.dto.RelationDto;
|
||||
import com.sismics.docs.core.dao.dto.RouteStepDto;
|
||||
import com.sismics.docs.core.dao.dto.TagDto;
|
||||
import com.sismics.docs.core.event.DocumentCreatedAsyncEvent;
|
||||
import com.sismics.docs.core.event.DocumentDeletedAsyncEvent;
|
||||
import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent;
|
||||
@@ -19,10 +29,15 @@ import com.sismics.docs.core.model.context.AppContext;
|
||||
import com.sismics.docs.core.model.jpa.Document;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import com.sismics.docs.core.util.*;
|
||||
import com.sismics.docs.core.util.ConfigUtil;
|
||||
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.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;
|
||||
@@ -33,55 +48,55 @@ import com.sismics.util.EmailUtil;
|
||||
import com.sismics.util.JsonUtil;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
import com.sismics.util.mime.MimeType;
|
||||
import jakarta.json.Json;
|
||||
import jakarta.json.JsonArrayBuilder;
|
||||
import jakarta.json.JsonObjectBuilder;
|
||||
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;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.StreamingOutput;
|
||||
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 jakarta.json.Json;
|
||||
import jakarta.json.JsonArrayBuilder;
|
||||
import jakarta.json.JsonObjectBuilder;
|
||||
import javax.mail.Message;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.StreamingOutput;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
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.
|
||||
*
|
||||
@@ -89,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
|
||||
@@ -112,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
|
||||
@@ -163,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()) {
|
||||
@@ -192,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));
|
||||
@@ -235,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);
|
||||
@@ -246,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);
|
||||
@@ -285,7 +285,7 @@ public class DocumentResource extends BaseResource {
|
||||
|
||||
return Response.ok().entity(document.build()).build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Export a document to PDF.
|
||||
*
|
||||
@@ -295,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
|
||||
@@ -307,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
|
||||
@@ -318,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();
|
||||
@@ -343,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 {
|
||||
@@ -358,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
|
||||
@@ -396,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
|
||||
@@ -417,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);
|
||||
@@ -438,11 +490,14 @@ public class DocumentResource extends BaseResource {
|
||||
}
|
||||
|
||||
// Find the files of the documents
|
||||
Iterable<String> documentsIds = CollectionUtils.collect(paginatedList.getResultList(), DocumentDto::getId);
|
||||
FileDao fileDao = new FileDao();
|
||||
List<File> filesList = null;
|
||||
Map<String, Long> filesCountByDocument = null;
|
||||
if (Boolean.TRUE == files) {
|
||||
Iterable<String> documentsIds = CollectionUtils.collect(paginatedList.getResultList(), DocumentDto::getId);
|
||||
FileDao fileDao = new FileDao();
|
||||
filesList = fileDao.getByDocumentsIds(documentsIds);
|
||||
} else {
|
||||
filesCountByDocument = fileDao.countByDocumentsIds(documentsIds);
|
||||
}
|
||||
|
||||
for (DocumentDto documentDto : paginatedList.getResultList()) {
|
||||
@@ -450,32 +505,26 @@ 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;
|
||||
if (Boolean.TRUE == files) {
|
||||
// Find files matching the document
|
||||
filesOfDocument = CollectionUtils.select(filesList, file -> file.getDocumentId().equals(documentDto.getId()));
|
||||
filesCount = (long) filesOfDocument.size();
|
||||
} else {
|
||||
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("file_count", documentDto.getFileCount())
|
||||
.add("tags", tags);
|
||||
.add("highlight", JsonUtil.nullable(documentDto.getHighlight()))
|
||||
.add("file_count", filesCount)
|
||||
.add("tags", createTagsArrayBuilder(tagDtoList));
|
||||
|
||||
if (Boolean.TRUE == files) {
|
||||
JsonArrayBuilder filesArrayBuilder = Json.createArrayBuilder();
|
||||
// Find files matching the document
|
||||
Collection<File> filesOfDocument = CollectionUtils.select(filesList, file -> file.getDocumentId().equals(documentDto.getId()));
|
||||
for (File fileDb : filesOfDocument) {
|
||||
filesArrayBuilder.add(RestUtil.fileToJsonObjectBuilder(fileDb));
|
||||
}
|
||||
@@ -492,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();
|
||||
}
|
||||
|
||||
@@ -521,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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -772,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);
|
||||
@@ -836,7 +741,7 @@ public class DocumentResource extends BaseResource {
|
||||
.add("id", document.getId());
|
||||
return Response.ok().entity(response.build()).build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the document.
|
||||
*
|
||||
@@ -858,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
|
||||
@@ -894,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);
|
||||
@@ -911,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);
|
||||
@@ -942,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);
|
||||
|
||||
@@ -963,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();
|
||||
@@ -1098,39 +1003,25 @@ public class DocumentResource extends BaseResource {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
List<File> fileList = fileDao.getByDocumentId(principal.getId(), id);
|
||||
|
||||
|
||||
// Delete the document
|
||||
documentDao.delete(id, principal.getId());
|
||||
|
||||
long totalSize = 0L;
|
||||
for (File file : fileList) {
|
||||
// Store the file size to update the quota
|
||||
java.nio.file.Path storedFile = DirectoryUtil.getStorageDirectory().resolve(file.getId());
|
||||
try {
|
||||
totalSize += Files.size(storedFile);
|
||||
} catch (IOException e) {
|
||||
// The file doesn't exists on disk, which is weird, but not fatal
|
||||
}
|
||||
|
||||
// Raise file deleted event
|
||||
FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
|
||||
fileDeletedAsyncEvent.setUserId(principal.getId());
|
||||
fileDeletedAsyncEvent.setFileId(file.getId());
|
||||
fileDeletedAsyncEvent.setFileSize(file.getSize());
|
||||
ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent);
|
||||
}
|
||||
|
||||
// Update the user quota
|
||||
UserDao userDao = new UserDao();
|
||||
User user = userDao.getById(principal.getId());
|
||||
user.setStorageCurrent(user.getStorageCurrent() - totalSize);
|
||||
userDao.updateQuota(user);
|
||||
|
||||
// Raise a document deleted event
|
||||
DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent();
|
||||
documentDeletedAsyncEvent.setUserId(principal.getId());
|
||||
documentDeletedAsyncEvent.setDocumentId(id);
|
||||
ThreadLocalContext.get().addAsyncEvent(documentDeletedAsyncEvent);
|
||||
|
||||
|
||||
// Always return OK
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
.add("status", "ok");
|
||||
@@ -1183,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
|
||||
@@ -442,7 +442,7 @@ public class FileResource extends BaseResource {
|
||||
/**
|
||||
* List all versions of a file.
|
||||
*
|
||||
* @api {get} /file/id/versions Get versions of a file
|
||||
* @api {get} /file/:id/versions Get versions of a file
|
||||
* @apiName GetFileVersions
|
||||
* @apiGroup File
|
||||
* @apiParam {String} id File ID
|
||||
@@ -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
|
||||
@@ -522,21 +521,11 @@ public class FileResource extends BaseResource {
|
||||
FileDao fileDao = new FileDao();
|
||||
fileDao.delete(file.getId(), principal.getId());
|
||||
|
||||
// Update the user quota
|
||||
UserDao userDao = new UserDao();
|
||||
User user = userDao.getById(principal.getId());
|
||||
java.nio.file.Path storedFile = DirectoryUtil.getStorageDirectory().resolve(id);
|
||||
try {
|
||||
user.setStorageCurrent(user.getStorageCurrent() - Files.size(storedFile));
|
||||
userDao.updateQuota(user);
|
||||
} catch (IOException e) {
|
||||
// The file doesn't exists on disk, which is weird, but not fatal
|
||||
}
|
||||
|
||||
// Raise a new file deleted event
|
||||
FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
|
||||
fileDeletedAsyncEvent.setUserId(principal.getId());
|
||||
fileDeletedAsyncEvent.setFileId(file.getId());
|
||||
fileDeletedAsyncEvent.setFileSize(file.getSize());
|
||||
ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent);
|
||||
|
||||
if (file.getDocumentId() != null) {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -30,8 +30,6 @@ import com.sismics.util.context.ThreadLocalContext;
|
||||
import com.sismics.util.filter.TokenBasedSecurityFilter;
|
||||
import com.sismics.util.totp.GoogleAuthenticator;
|
||||
import com.sismics.util.totp.GoogleAuthenticatorKey;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import jakarta.json.Json;
|
||||
import jakarta.json.JsonArrayBuilder;
|
||||
import jakarta.json.JsonObjectBuilder;
|
||||
@@ -40,6 +38,8 @@ import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.NewCookie;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -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,
|
||||
@@ -256,39 +256,6 @@ public class UserResource extends BaseResource {
|
||||
return Response.ok().entity(response.build()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a username is available.
|
||||
* Search only on active accounts.
|
||||
*
|
||||
* @api {get} /user/check_username Check username availability
|
||||
* @apiName GetUserCheckUsername
|
||||
* @apiGroup User
|
||||
* @apiParam {String} username Username
|
||||
* @apiSuccess {String} status Status OK or KO
|
||||
* @apiPermission none
|
||||
* @apiVersion 1.5.0
|
||||
*
|
||||
* @param username Username to check
|
||||
* @return Response
|
||||
*/
|
||||
@GET
|
||||
@Path("check_username")
|
||||
public Response checkUsername(
|
||||
@QueryParam("username") String username) {
|
||||
UserDao userDao = new UserDao();
|
||||
User user = userDao.getActiveByUsername(username);
|
||||
|
||||
JsonObjectBuilder response = Json.createObjectBuilder();
|
||||
if (user != null) {
|
||||
response.add("status", "ko")
|
||||
.add("message", "Username already registered");
|
||||
} else {
|
||||
response.add("status", "ok");
|
||||
}
|
||||
|
||||
return Response.ok().entity(response.build()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* This resource is used to authenticate the user and create a user session.
|
||||
* The "session" is only used to identify the user, no other data is stored in the session.
|
||||
@@ -470,22 +437,8 @@ public class UserResource extends BaseResource {
|
||||
UserDao userDao = new UserDao();
|
||||
userDao.delete(principal.getName(), principal.getId());
|
||||
|
||||
// Raise deleted events for documents
|
||||
for (Document document : documentList) {
|
||||
DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent();
|
||||
documentDeletedAsyncEvent.setUserId(principal.getId());
|
||||
documentDeletedAsyncEvent.setDocumentId(document.getId());
|
||||
ThreadLocalContext.get().addAsyncEvent(documentDeletedAsyncEvent);
|
||||
}
|
||||
|
||||
// Raise deleted events for files (don't bother sending document updated event)
|
||||
for (File file : fileList) {
|
||||
FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
|
||||
fileDeletedAsyncEvent.setUserId(principal.getId());
|
||||
fileDeletedAsyncEvent.setFileId(file.getId());
|
||||
ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent);
|
||||
}
|
||||
|
||||
sendDeletionEvents(documentList, fileList);
|
||||
|
||||
// Always return OK
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
.add("status", "ok");
|
||||
@@ -511,7 +464,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();
|
||||
@@ -551,23 +504,9 @@ public class UserResource extends BaseResource {
|
||||
|
||||
// Delete the user
|
||||
userDao.delete(user.getUsername(), principal.getId());
|
||||
|
||||
// Raise deleted events for documents
|
||||
for (Document document : documentList) {
|
||||
DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent();
|
||||
documentDeletedAsyncEvent.setUserId(principal.getId());
|
||||
documentDeletedAsyncEvent.setDocumentId(document.getId());
|
||||
ThreadLocalContext.get().addAsyncEvent(documentDeletedAsyncEvent);
|
||||
}
|
||||
|
||||
// Raise deleted events for files (don't bother sending document updated event)
|
||||
for (File file : fileList) {
|
||||
FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
|
||||
fileDeletedAsyncEvent.setUserId(principal.getId());
|
||||
fileDeletedAsyncEvent.setFileId(file.getId());
|
||||
ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent);
|
||||
}
|
||||
|
||||
|
||||
sendDeletionEvents(documentList, fileList);
|
||||
|
||||
// Always return OK
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
.add("status", "ok");
|
||||
@@ -591,7 +530,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();
|
||||
@@ -713,7 +652,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()) {
|
||||
@@ -1178,4 +1117,29 @@ public class UserResource extends BaseResource {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the events about documents and files being deleted.
|
||||
* @param documentList A document list
|
||||
* @param fileList A file list
|
||||
*/
|
||||
private void sendDeletionEvents(List<Document> documentList, List<File> fileList) {
|
||||
// Raise deleted events for documents
|
||||
for (Document document : documentList) {
|
||||
DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent();
|
||||
documentDeletedAsyncEvent.setUserId(principal.getId());
|
||||
documentDeletedAsyncEvent.setDocumentId(document.getId());
|
||||
ThreadLocalContext.get().addAsyncEvent(documentDeletedAsyncEvent);
|
||||
}
|
||||
|
||||
// Raise deleted events for files (don't bother sending document updated event)
|
||||
for (File file : fileList) {
|
||||
FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
|
||||
fileDeletedAsyncEvent.setUserId(principal.getId());
|
||||
fileDeletedAsyncEvent.setFileId(file.getId());
|
||||
fileDeletedAsyncEvent.setFileSize(file.getSize());
|
||||
ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package com.sismics.docs.core.util;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -39,10 +40,10 @@ public class TagUtil {
|
||||
* @return List of filtered tags
|
||||
*/
|
||||
public static List<TagDto> findByName(String name, List<TagDto> allTagDtoList) {
|
||||
List<TagDto> tagDtoList = new ArrayList<>();
|
||||
if (name.isEmpty()) {
|
||||
return tagDtoList;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<TagDto> tagDtoList = new ArrayList<>();
|
||||
name = name.toLowerCase();
|
||||
for (TagDto tagDto : allTagDtoList) {
|
||||
if (tagDto.getName().toLowerCase().startsWith(name)) {
|
||||
@@ -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`)
|
||||
|
||||
@@ -429,7 +429,7 @@ angular.module('docs',
|
||||
prefix: 'locale/',
|
||||
suffix: '.json?@build.date@'
|
||||
})
|
||||
.registerAvailableLanguageKeys(['en', 'es', 'pt', 'fr', 'de', 'el', 'ru', 'it', 'pl', 'zh_CN', 'zh_TW'], {
|
||||
.registerAvailableLanguageKeys(['en', 'es', 'pt', 'fr', 'de', 'el', 'ru', 'it', 'pl', 'zh_CN', 'zh_TW', 'sq_AL'], {
|
||||
'en_*': 'en',
|
||||
'es_*': 'es',
|
||||
'pt_*': 'pt',
|
||||
@@ -547,7 +547,8 @@ angular.module('docs',
|
||||
{ key: 'dan', label: 'Dansk' },
|
||||
{ key: 'nor', label: 'Norsk' },
|
||||
{ key: 'vie', label: 'Tiếng Việt' },
|
||||
{ key: 'ces', label: 'Czech' }
|
||||
{ key: 'ces', label: 'Czech' },
|
||||
{ key: 'sqi', label: 'Shqip' }
|
||||
];
|
||||
})
|
||||
/**
|
||||
|
||||
@@ -21,6 +21,15 @@ angular.module('docs').controller('SettingsConfig', function($scope, $rootScope,
|
||||
});
|
||||
};
|
||||
|
||||
// Enable/disable OCR
|
||||
$scope.changeOcrEnabled = function (enabled) {
|
||||
Restangular.one('app').post('ocr', {
|
||||
enabled: enabled
|
||||
}).then(function () {
|
||||
$scope.app.ocr_enabled = enabled;
|
||||
});
|
||||
};
|
||||
|
||||
// Fetch the current theme configuration
|
||||
Restangular.one('theme').get().then(function (data) {
|
||||
$scope.theme = data;
|
||||
@@ -36,7 +45,7 @@ angular.module('docs').controller('SettingsConfig', function($scope, $rootScope,
|
||||
$rootScope.appName = $scope.theme.name;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Send an image
|
||||
$scope.sendingImage = false;
|
||||
$scope.sendImage = function (type, image) {
|
||||
@@ -108,4 +117,4 @@ angular.module('docs').controller('SettingsConfig', function($scope, $rootScope,
|
||||
$scope.loadWebhooks();
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
@@ -61,7 +61,7 @@ angular.module('share',
|
||||
prefix: 'locale/',
|
||||
suffix: '.json?@build.date@'
|
||||
})
|
||||
.registerAvailableLanguageKeys(['en', 'es', 'pt', 'fr', 'de', 'el', 'ru', 'it', 'pl', 'zh_CN', 'zh_TW'], {
|
||||
.registerAvailableLanguageKeys(['en', 'es', 'pt', 'fr', 'de', 'el', 'ru', 'it', 'pl', 'zh_CN', 'zh_TW', 'sq_AL'], {
|
||||
'en_*': 'en',
|
||||
'es_*': 'es',
|
||||
'pt_*': 'pt',
|
||||
|
||||
@@ -4,6 +4,15 @@
|
||||
* Share controller.
|
||||
*/
|
||||
angular.module('share').controller('Share', function($scope, $state, $stateParams, Restangular, $uibModal) {
|
||||
$scope.displayMode = _.isUndefined(localStorage.fileDisplayMode) ? 'grid' : localStorage.fileDisplayMode;
|
||||
|
||||
/**
|
||||
* Watch for display mode change.
|
||||
*/
|
||||
$scope.$watch('displayMode', function (next) {
|
||||
localStorage.fileDisplayMode = next;
|
||||
});
|
||||
|
||||
// Load document
|
||||
Restangular.one('document', $stateParams.documentId).get({ share: $stateParams.shareId })
|
||||
.then(function (data) {
|
||||
|
||||
@@ -192,6 +192,7 @@
|
||||
<span ng-switch-when="pl">Polski</span>
|
||||
<span ng-switch-when="zh_CN">简体中文</span>
|
||||
<span ng-switch-when="zh_TW">繁體中文</span>
|
||||
<span ng-switch-when="sq_AL">Shqip</span>
|
||||
</span>
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
@@ -207,6 +208,7 @@
|
||||
<li><a href ng-click="changeLanguage('pl')" ng-class="{ 'bg-info': currentLang == 'pl' }">Polski</a></li>
|
||||
<li><a href ng-click="changeLanguage('zh_CN')" ng-class="{ 'bg-info': currentLang == 'zh_CN' }">简体中文</a></li>
|
||||
<li><a href ng-click="changeLanguage('zh_TW')" ng-class="{ 'bg-info': currentLang == 'zh_TW' }">繁體中文</a></li>
|
||||
<li><a href ng-click="changeLanguage('sq_AL')" ng-class="{ 'bg-info': currentLang == 'sq_AL' }">Shqip</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li translate="document.default.footer_sismics"></li>
|
||||
|
||||
150
docs-web/src/main/webapp/src/locale/angular-locale_sq_AL.js
vendored
Normal file
150
docs-web/src/main/webapp/src/locale/angular-locale_sq_AL.js
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
'use strict';
|
||||
angular.module("ngLocale", [], ["$provide", function($provide) {
|
||||
var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
|
||||
function getDecimals(n) {
|
||||
n = n + '';
|
||||
var i = n.indexOf('.');
|
||||
return (i == -1) ? 0 : n.length - i - 1;
|
||||
}
|
||||
|
||||
function getVF(n, opt_precision) {
|
||||
var v = opt_precision;
|
||||
|
||||
if (undefined === v) {
|
||||
v = Math.min(getDecimals(n), 3);
|
||||
}
|
||||
|
||||
var base = Math.pow(10, v);
|
||||
var f = ((n * base) | 0) % base;
|
||||
return {v: v, f: f};
|
||||
}
|
||||
|
||||
$provide.value("$locale", {
|
||||
"DATETIME_FORMATS": {
|
||||
"AMPMS": [
|
||||
"PD",
|
||||
"MD"
|
||||
],
|
||||
"DAY": [
|
||||
"E Diel",
|
||||
"E Hënë",
|
||||
"E Martë",
|
||||
"E Mërkurë",
|
||||
"E Enjte",
|
||||
"E Premte",
|
||||
"E Shtunë"
|
||||
],
|
||||
"ERANAMES": [
|
||||
"Para Krishtit",
|
||||
"Pas Krishtit"
|
||||
],
|
||||
"ERAS": [
|
||||
"p.K.",
|
||||
"n.K."
|
||||
],
|
||||
"FIRSTDAYOFWEEK": 1,
|
||||
"MONTH": [
|
||||
"Janar",
|
||||
"Shkurt",
|
||||
"Mars",
|
||||
"Prill",
|
||||
"Maj",
|
||||
"Qershor",
|
||||
"Korrik",
|
||||
"Gusht",
|
||||
"Shtator",
|
||||
"Tetor",
|
||||
"Nëntor",
|
||||
"Dhjetor"
|
||||
],
|
||||
"SHORTDAY": [
|
||||
"Die",
|
||||
"Hën",
|
||||
"Mar",
|
||||
"Mër",
|
||||
"Enj",
|
||||
"Pre",
|
||||
"Sht"
|
||||
],
|
||||
"SHORTMONTH": [
|
||||
"Jan",
|
||||
"Shk",
|
||||
"Mar",
|
||||
"Pri",
|
||||
"Maj",
|
||||
"Qer",
|
||||
"Kor",
|
||||
"Gus",
|
||||
"Sht",
|
||||
"Tet",
|
||||
"Nën",
|
||||
"Dhj"
|
||||
],
|
||||
"STANDALONEMONTH": [
|
||||
"Janar",
|
||||
"Shkurt",
|
||||
"Mars",
|
||||
"Prill",
|
||||
"Maj",
|
||||
"Qershor",
|
||||
"Korrik",
|
||||
"Gusht",
|
||||
"Shtator",
|
||||
"Tetor",
|
||||
"Nëntor",
|
||||
"Dhjetor"
|
||||
],
|
||||
"WEEKENDRANGE": [
|
||||
6,
|
||||
0
|
||||
],
|
||||
"fullDate": "EEEE, d MMMM y",
|
||||
"longDate": "d MMMM y",
|
||||
"medium": "d MMM y h:mm:ss a",
|
||||
"mediumDate": "d MMM y",
|
||||
"mediumTime": "h:mm:ss a",
|
||||
"short": "yy-MM-dd h:mm a",
|
||||
"shortDate": "yy-MM-dd",
|
||||
"shortTime": "h:mm a"
|
||||
},
|
||||
"NUMBER_FORMATS": {
|
||||
"CURRENCY_SYM": "Lek",
|
||||
"DECIMAL_SEP": ".",
|
||||
"GROUP_SEP": ",",
|
||||
"PATTERNS": [
|
||||
{
|
||||
"gSize": 3,
|
||||
"lgSize": 3,
|
||||
"maxFrac": 3,
|
||||
"minFrac": 0,
|
||||
"minInt": 1,
|
||||
"negPre": "-",
|
||||
"negSuf": "",
|
||||
"posPre": "",
|
||||
"posSuf": ""
|
||||
},
|
||||
{
|
||||
"gSize": 3,
|
||||
"lgSize": 3,
|
||||
"maxFrac": 2,
|
||||
"minFrac": 2,
|
||||
"minInt": 1,
|
||||
"negPre": "-\u00a4",
|
||||
"negSuf": "",
|
||||
"posPre": "\u00a4",
|
||||
"posSuf": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "sq-al",
|
||||
"localeID": "sq_AL",
|
||||
"pluralCat": function(n, opt_precision) {
|
||||
var i = n | 0;
|
||||
var vf = getVF(n, opt_precision);
|
||||
if (i == 1 && vf.v == 0) {
|
||||
return PLURAL_CATEGORY.ONE;
|
||||
}
|
||||
return PLURAL_CATEGORY.OTHER;
|
||||
}
|
||||
});
|
||||
}]);
|
||||
@@ -421,6 +421,10 @@
|
||||
"smtp_username": "SMTP-Benutzername",
|
||||
"smtp_password": "SMTP-Passwort",
|
||||
"smtp_updated": "SMTP-Konfiguration erfolgreich aktualisiert",
|
||||
"title_ocr": "Texterkennung (OCR)",
|
||||
"message_ocr": "OCR ist eine Technologie, die es ermöglicht, Text aus Bildern zu extrahieren. Es wird verwendet, um den Inhalt von Bildern zu durchsuchen und zu indizieren. Diese Funktionalität setzt voraus, dass die Tesseract auf Ihrem Server installiert ist.",
|
||||
"enable_ocr": "OCR aktivieren",
|
||||
"disable_ocr": "OCR deaktivieren",
|
||||
"webhooks": "Webhooks",
|
||||
"webhooks_explain": "Webhooks werden aufgerufen, wenn das angegebene Ereignis eintritt. Die angegebene URL wird mit einer JSON-Payload gepostet, die den Ereignisnamen und die ID der betreffenden Ressource enthält.",
|
||||
"webhook_event": "Ereignisse",
|
||||
|
||||
@@ -421,6 +421,10 @@
|
||||
"smtp_username": "SMTP username",
|
||||
"smtp_password": "SMTP password",
|
||||
"smtp_updated": "SMTP configuration updated successfully",
|
||||
"title_ocr": "Optical Character Recognition (OCR)",
|
||||
"message_ocr": "OCR is a feature that extracts text from images and PDF files. This feature requires a working Tesseract installation on the server.",
|
||||
"enable_ocr": "Enable OCR",
|
||||
"disable_ocr": "Disable OCR",
|
||||
"webhooks": "Webhooks",
|
||||
"webhooks_explain": "Webhooks will be called when the specified event occur. The given URL will be POST-ed with a JSON payload containing the event name and the ID of the concerned resource.",
|
||||
"webhook_event": "Event",
|
||||
|
||||
640
docs-web/src/main/webapp/src/locale/sq_AL.json
Normal file
640
docs-web/src/main/webapp/src/locale/sq_AL.json
Normal file
@@ -0,0 +1,640 @@
|
||||
{
|
||||
"login": {
|
||||
"username": "Emri i përdoruesit",
|
||||
"password": "Fjalëkalimi",
|
||||
"validation_code_required": "Kërkohet një kod verifikimi",
|
||||
"validation_code_title": "Ju keni aktivizuar vërtetimin me dy faktorë në llogarinë tuaj. ",
|
||||
"validation_code": "Kodi i verifikimit",
|
||||
"remember_me": "Më kujto mua",
|
||||
"submit": "Hyni",
|
||||
"login_as_guest": "Identifikohu si i ftuar",
|
||||
"login_failed_title": "Identifikimi dështoi",
|
||||
"login_failed_message": "Emri i përdoruesit ose fjalëkalimi është i pavlefshëm",
|
||||
"password_lost_btn": "Fjalëkalimi i humbur?",
|
||||
"password_lost_sent_title": "Email për rivendosjen e fjalëkalimit u dërgua",
|
||||
"password_lost_sent_message": "Një email është dërguar në <strong>{{ username }}</strong> për të rivendosur fjalëkalimin tuaj",
|
||||
"password_lost_error_title": "Gabim i rivendosjes së fjalëkalimit",
|
||||
"password_lost_error_message": "Nuk mund të dërgohet një email për rivendosjen e fjalëkalimit, ju lutemi kontaktoni administratorin tuaj për një rivendosje manuale"
|
||||
},
|
||||
"passwordlost": {
|
||||
"title": "Fjalëkalimi ka humbur",
|
||||
"message": "Ju lutemi shkruani emrin tuaj të përdoruesit për të marrë një lidhje të rivendosjes së fjalëkalimit. ",
|
||||
"submit": "Rivendos fjalëkalimin tim"
|
||||
},
|
||||
"passwordreset": {
|
||||
"message": "Ju lutemi shkruani një fjalëkalim të ri",
|
||||
"submit": "Ndrysho fjalëkalimin tim",
|
||||
"error_title": "Gabim gjatë ndryshimit të fjalëkalimit tuaj",
|
||||
"error_message": "Kërkesa juaj për rikuperimin e fjalëkalimit ka skaduar, ju lutemi kërkoni një të re në faqen e hyrjes"
|
||||
},
|
||||
"index": {
|
||||
"toggle_navigation": "Ndrysho navigimin",
|
||||
"nav_documents": "Dokumentet",
|
||||
"nav_tags": "Etiketa",
|
||||
"nav_users_groups": "Përdoruesit",
|
||||
"error_info": "{{ count }} gabim i ri{{ count > 1 ? 's' : '' }}",
|
||||
"logged_as": "I identifikuar si {{ username }}",
|
||||
"nav_settings": "Cilësimet",
|
||||
"logout": "Shkyç",
|
||||
"global_quota_warning": "<strong>Paralajmërim!</strong> Kuota globale pothuajse arriti në {{ current | number: 0 }}MB ({{ percent | number: 1 }}%) përdoret në {{ total | number: 0 }}MB"
|
||||
},
|
||||
"document": {
|
||||
"navigation_up": "Ngjitu një nivel",
|
||||
"toggle_navigation": "Ndrysho navigimin e dosjeve",
|
||||
"display_mode_list": "Shfaq dokumentet në listë",
|
||||
"display_mode_grid": "Shfaq dokumentet në rrjet",
|
||||
"search_simple": "Kërkim i thjeshtë",
|
||||
"search_fulltext": "Kërkimi i tekstit të plotë",
|
||||
"search_creator": "Krijuesi",
|
||||
"search_language": "Gjuhe",
|
||||
"search_before_date": "Krijuar para kësaj date",
|
||||
"search_after_date": "Krijuar pas kësaj date",
|
||||
"search_before_update_date": "Përditësuar përpara kësaj date",
|
||||
"search_after_update_date": "Përditësuar pas kësaj date",
|
||||
"search_tags": "Etiketa",
|
||||
"search_shared": "Vetëm dokumente të përbashkëta",
|
||||
"search_workflow": "Rrjedha e punës më është caktuar",
|
||||
"search_clear": "Qartë",
|
||||
"any_language": "Çdo gjuhë",
|
||||
"add_document": "Shto një dokument",
|
||||
"import_eml": "Importo nga një email (format EML)",
|
||||
"tags": "Etiketa",
|
||||
"no_tags": "Nuk ka etiketa",
|
||||
"no_documents": "Asnjë dokument në bazën e të dhënave",
|
||||
"search": "Kërko",
|
||||
"search_empty": "Nuk ka ndeshje për <strong>\"{{ search }}\"</strong>",
|
||||
"shared": "Të përbashkëta",
|
||||
"current_step_name": "Hapi aktual",
|
||||
"title": "Titulli",
|
||||
"description": "Përshkrim",
|
||||
"contributors": "Kontribuesit",
|
||||
"language": "Gjuhe",
|
||||
"creation_date": "Data e krijimit",
|
||||
"subject": "Subjekti",
|
||||
"identifier": "Identifikues",
|
||||
"publisher": "Botues",
|
||||
"format": "Formati",
|
||||
"source": "Burimi",
|
||||
"type": "Lloji",
|
||||
"coverage": "Mbulimi",
|
||||
"rights": "Të drejtat",
|
||||
"relations": "Marrëdhëniet",
|
||||
"page_size": "Madhësia e faqes",
|
||||
"page_size_10": "10 për faqe",
|
||||
"page_size_20": "20 për faqe",
|
||||
"page_size_30": "30 për faqe",
|
||||
"upgrade_quota": "Për të përmirësuar kuotën tuaj, pyesni administratorin tuaj",
|
||||
"quota": "{{ current | number: 0 }}MB ({{ percent | number: 1 }}%) përdoret në {{ total | number: 0 }}MB",
|
||||
"count": "{{ count }} dokument{{ count > 1 ? 's' : '' }} gjetur",
|
||||
"last_updated": "Përditësimi i fundit {{ date | timeAgo: dateFormat }}",
|
||||
"view": {
|
||||
"delete_comment_title": "Fshi komentin",
|
||||
"delete_comment_message": "Dëshiron vërtet ta fshish këtë koment?",
|
||||
"delete_document_title": "Fshi dokumentin",
|
||||
"delete_document_message": "Dëshiron vërtet ta fshish këtë dokument?",
|
||||
"shared_document_title": "Dokument i përbashkët",
|
||||
"shared_document_message": "Ju mund ta ndani këtë dokument duke dhënë këtë lidhje. <br/><input class=\"form-control share-link\" type=\"text\" readonly=\"readonly\" value=\"{{ link }}\" onclick=\"this.select(); document.execCommand('copy');\" />",
|
||||
"not_found": "Dokumenti nuk u gjet",
|
||||
"forbidden": "Qasja është e ndaluar",
|
||||
"download_files": "Shkarko skedarët",
|
||||
"export_pdf": "Eksporto në PDF",
|
||||
"by_creator": "nga",
|
||||
"comments": "Komentet",
|
||||
"no_comments": "Ende nuk ka komente për këtë dokument",
|
||||
"add_comment": "Shto një koment",
|
||||
"error_loading_comments": "Gabim gjatë ngarkimit të komenteve",
|
||||
"workflow_current": "Hapi aktual i rrjedhës së punës",
|
||||
"workflow_comment": "Shto një koment të rrjedhës së punës",
|
||||
"workflow_validated_title": "Hapi i rrjedhës së punës u vërtetua",
|
||||
"workflow_validated_message": "Hapi i rrjedhës së punës është vërtetuar me sukses.",
|
||||
"content": {
|
||||
"content": "përmbajtja",
|
||||
"delete_file_title": "Fshi skedarin",
|
||||
"delete_file_message": "Dëshiron vërtet ta fshish këtë skedar?",
|
||||
"upload_pending": "Në pritje...",
|
||||
"upload_progress": "Po ngarkohet...",
|
||||
"upload_error": "Gabim ngarkimi",
|
||||
"upload_error_quota": "Kuota u arrit",
|
||||
"drop_zone": "Zvarrit",
|
||||
"add_files": "Shtoni skedarë",
|
||||
"file_processing_indicator": "Ky skedar është duke u përpunuar. ",
|
||||
"reprocess_file": "Ripërpunoni këtë skedar",
|
||||
"upload_new_version": "Ngarko një version të ri",
|
||||
"open_versions": "Shfaq historikun e versionit",
|
||||
"display_mode_list": "Shfaq skedarët në listë",
|
||||
"display_mode_grid": "Shfaq skedarët në rrjet"
|
||||
},
|
||||
"workflow": {
|
||||
"workflow": "Rrjedha e punës",
|
||||
"message": "Verifikoni ose vërtetoni dokumentet tuaja me njerëzit e organizatës suaj duke përdorur rrjedhat e punës.",
|
||||
"workflow_start_label": "Cilin rrjedhë pune të filloni?",
|
||||
"add_more_workflow": "Shto më shumë flukse pune",
|
||||
"start_workflow_submit": "Filloni rrjedhën e punës",
|
||||
"full_name": "<strong>{{ name }}</strong> filloi më {{ create_date | date }}",
|
||||
"cancel_workflow": "Anuloni rrjedhën aktuale të punës",
|
||||
"cancel_workflow_title": "Anuloni rrjedhën e punës",
|
||||
"cancel_workflow_message": "Dëshiron vërtet të anulosh rrjedhën aktuale të punës?",
|
||||
"no_workflow": "Nuk mund të filloni asnjë rrjedhë pune në këtë dokument."
|
||||
},
|
||||
"permissions": {
|
||||
"permissions": "Lejet",
|
||||
"message": "Lejet mund të aplikohen drejtpërdrejt në këtë dokument, ose mund të vijnë nga <a href=\"#/tag\">etiketa</a>.",
|
||||
"title": "Lejet për këtë dokument",
|
||||
"inherited_tags": "Lejet e trashëguara nga etiketat",
|
||||
"acl_source": "Nga",
|
||||
"acl_target": "Për",
|
||||
"acl_permission": "Leja"
|
||||
},
|
||||
"activity": {
|
||||
"activity": "Aktiviteti",
|
||||
"message": "Çdo veprim në këtë dokument regjistrohet këtu."
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
"document_edited_with_errors": "Dokumenti u redaktua me sukses, por disa skedarë nuk mund të ngarkohen",
|
||||
"document_added_with_errors": "Dokumenti u shtua me sukses, por disa skedarë nuk mund të ngarkohen",
|
||||
"quota_reached": "Kuota u arrit",
|
||||
"primary_metadata": "Meta të dhënat primare",
|
||||
"title_placeholder": "Një emër i dhënë burimit",
|
||||
"description_placeholder": "Një llogari e burimit",
|
||||
"new_files": "Skedarë të rinj",
|
||||
"orphan_files": "{{ count }} dosje{{ count > 1 ? 's' : '' }}",
|
||||
"additional_metadata": "Meta të dhëna shtesë",
|
||||
"subject_placeholder": "Tema e burimit",
|
||||
"identifier_placeholder": "Një referencë e paqartë për burimin brenda një konteksti të caktuar",
|
||||
"publisher_placeholder": "Një subjekt përgjegjës për vënien në dispozicion të burimit",
|
||||
"format_placeholder": "Formati i skedarit, mediumi fizik ose dimensionet e burimit",
|
||||
"source_placeholder": "Një burim i lidhur nga i cili rrjedh burimi i përshkruar",
|
||||
"uploading_files": "Skedarët po ngarkohen..."
|
||||
},
|
||||
"default": {
|
||||
"upload_pending": "Në pritje...",
|
||||
"upload_progress": "Po ngarkohet...",
|
||||
"upload_error": "Gabim ngarkimi",
|
||||
"upload_error_quota": "Kuota u arrit",
|
||||
"quick_upload": "Ngarkimi i shpejtë",
|
||||
"drop_zone": "Zvarrit",
|
||||
"add_files": "Shtoni skedarë",
|
||||
"add_new_document": "Shto në dokument të ri",
|
||||
"latest_activity": "Aktiviteti i fundit",
|
||||
"footer_sismics": "E punuar me <span class=\"fas fa-heart\"></span> nga <a href=\"https://www.sismics.com\" target=\"_blank\">Sizmike</a>",
|
||||
"api_documentation": "Dokumentacioni API",
|
||||
"feedback": "Na jepni një koment",
|
||||
"workflow_document_list": "Dokumentet e caktuara për ju",
|
||||
"select_all": "Selektoj të gjitha",
|
||||
"select_none": "Zgjidh asnjë"
|
||||
},
|
||||
"pdf": {
|
||||
"export_title": "Eksporto në PDF",
|
||||
"export_metadata": "Eksporto të dhëna meta",
|
||||
"export_comments": "Eksporto komente",
|
||||
"fit_to_page": "Përshtat imazhin në faqe",
|
||||
"margin": "Marzhi",
|
||||
"millimeter": "mm"
|
||||
},
|
||||
"share": {
|
||||
"title": "Ndani dokumentin",
|
||||
"message": "Emërtoni ndarjen nëse dëshironi të ndani disa herë të njëjtin dokument.",
|
||||
"submit": "Shpërndaje"
|
||||
}
|
||||
},
|
||||
"file": {
|
||||
"view": {
|
||||
"previous": "E mëparshme",
|
||||
"next": "Tjetra",
|
||||
"not_found": "Skedari nuk u gjet"
|
||||
},
|
||||
"edit": {
|
||||
"title": "Redakto skedarin",
|
||||
"name": "Emri i skedarit"
|
||||
},
|
||||
"versions": {
|
||||
"title": "Historia e versionit",
|
||||
"filename": "Emri i skedarit",
|
||||
"mimetype": "Lloji",
|
||||
"create_date": "Data e krijimit",
|
||||
"version": "Version"
|
||||
}
|
||||
},
|
||||
"tag": {
|
||||
"new_tag": "Etiketë e re",
|
||||
"search": "Kërko",
|
||||
"default": {
|
||||
"title": "Etiketa",
|
||||
"message_1": "<strong>Etiketa</strong> janë etiketa të lidhura me dokumentet.",
|
||||
"message_2": "Një dokument mund të etiketohet me etiketa të shumta dhe një etiketë mund të aplikohet në dokumente të shumta.",
|
||||
"message_3": "Duke perdorur <span class=\"fas fa-pencil-alt\"></span> butonin, ju mund të modifikoni lejet në një etiketë.",
|
||||
"message_4": "Nëse një etiketë mund të lexohet nga një përdorues ose grup tjetër, dokumentet shoqëruese mund të lexohen gjithashtu nga ata njerëz.",
|
||||
"message_5": "Për shembull, etiketoni dokumentet e kompanisë suaj me një etiketë <span class=\"label label-info\">Kompania ime</span> dhe shtoni lejen <strong>Mund të lexojë</strong> në një grup <span class=\"btn btn-default\">punonjësit</span>"
|
||||
},
|
||||
"edit": {
|
||||
"delete_tag_title": "Fshi etiketën",
|
||||
"delete_tag_message": "Dëshiron vërtet ta fshish këtë etiketë?",
|
||||
"name": "Emri",
|
||||
"color": "Ngjyrë",
|
||||
"parent": "Prindi",
|
||||
"info": "Lejet për këtë etiketë do të zbatohen gjithashtu për dokumentet e etiketuara <span class=\"label label-info\" ng-style=\"{ 'background': color }\">{{ name }}</span>",
|
||||
"circular_reference_title": "Referencë rrethore",
|
||||
"circular_reference_message": "Hierarkia e etiketave prind krijon një lak, ju lutemi zgjidhni një prind tjetër."
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"profile": {
|
||||
"members": "Anëtarët",
|
||||
"no_members": "Asnjë anëtar",
|
||||
"related_links": "Lidhje të ngjashme",
|
||||
"edit_group": "Redakto {{ name }} grup"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"profile": {
|
||||
"groups": "Grupet",
|
||||
"quota_used": "Kuota e përdorur",
|
||||
"percent_used": "{{ percent | number: 0 }}% e përdorur",
|
||||
"related_links": "Lidhje të ngjashme",
|
||||
"document_created": "Dokumentet e krijuara nga {{ username }}",
|
||||
"edit_user": "Redakto {{ username }} përdorues"
|
||||
}
|
||||
},
|
||||
"usergroup": {
|
||||
"search_groups": "Kërkoni në grupe",
|
||||
"search_users": "Kërkoni në përdoruesit",
|
||||
"you": "je ti!",
|
||||
"default": {
|
||||
"title": "Përdoruesit",
|
||||
"message": "Këtu mund të shikoni informacione rreth përdoruesve dhe grupeve."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"menu_personal_settings": "Cilësimet personale",
|
||||
"menu_user_account": "Llogaria e përdoruesit",
|
||||
"menu_two_factor_auth": "Autentifikimi me dy faktorë",
|
||||
"menu_opened_sessions": "Seancat e hapura",
|
||||
"menu_file_importer": "Importuesi i skedarëve në masë",
|
||||
"menu_general_settings": "Cilësimet e përgjithshme",
|
||||
"menu_workflow": "Rrjedha e punës",
|
||||
"menu_users": "Përdoruesit",
|
||||
"menu_groups": "Grupet",
|
||||
"menu_vocabularies": "Fjalorët",
|
||||
"menu_configuration": "Konfigurimi",
|
||||
"menu_inbox": "Skanimi i kutisë hyrëse",
|
||||
"menu_ldap": "Autentifikimi LDAP",
|
||||
"menu_metadata": "Meta të dhëna të personalizuara",
|
||||
"menu_monitoring": "Monitorimi",
|
||||
"ldap": {
|
||||
"title": "Autentifikimi LDAP",
|
||||
"enabled": "Aktivizo vërtetimin LDAP",
|
||||
"host": "Emri i hostit LDAP",
|
||||
"port": "Porta LDAP (389 si parazgjedhje)",
|
||||
"usessl": "Aktivizo SSL (ldaps)",
|
||||
"admin_dn": "Admin DN",
|
||||
"admin_password": "Fjalëkalimi i administratorit",
|
||||
"base_dn": "Kërkimi bazë DN",
|
||||
"filter": "Filtri i kërkimit (duhet të përmbajë USERNAME, p.sh. \"(uid=USERNAME)\")",
|
||||
"default_email": "Email-i i parazgjedhur për përdoruesin LDAP",
|
||||
"default_storage": "Hapësira ruajtëse e paracaktuar për përdoruesin LDAP",
|
||||
"saved": "Konfigurimi LDAP u ruajt me sukses"
|
||||
},
|
||||
"user": {
|
||||
"title": "Menaxhimi i përdoruesve",
|
||||
"add_user": "Shto një përdorues",
|
||||
"username": "Emri i përdoruesit",
|
||||
"create_date": "Krijo datë",
|
||||
"totp_enabled": "Për këtë llogari është aktivizuar vërtetimi me dy faktorë",
|
||||
"edit": {
|
||||
"delete_user_title": "Fshi përdoruesin",
|
||||
"delete_user_message": "Dëshiron vërtet ta fshish këtë përdorues? ",
|
||||
"user_used_title": "Përdoruesi në përdorim",
|
||||
"user_used_message": "Ky përdorues përdoret në rrjedhën e punës \"{{ name }}\"",
|
||||
"edit_user_failed_title": "Përdoruesi ekziston tashmë",
|
||||
"edit_user_failed_message": "Ky emër përdoruesi është marrë tashmë nga një përdorues tjetër",
|
||||
"edit_user_title": "Redakto \"{{ username }}\"",
|
||||
"add_user_title": "Shto një përdorues",
|
||||
"username": "Emri i përdoruesit",
|
||||
"email": "E-mail",
|
||||
"groups": "Grupet",
|
||||
"storage_quota": "Kuota e ruajtjes",
|
||||
"storage_quota_placeholder": "Kuota e hapësirës ruajtëse (në MB)",
|
||||
"password": "Fjalëkalimi",
|
||||
"password_confirm": "Fjalëkalimi (konfirmo)",
|
||||
"disabled": "Përdorues me aftësi të kufizuara",
|
||||
"password_reset_btn": "Dërgoni një email për rivendosjen e fjalëkalimit te ky përdorues",
|
||||
"password_lost_sent_title": "Email për rivendosjen e fjalëkalimit u dërgua",
|
||||
"password_lost_sent_message": "Është dërguar një email për rivendosjen e fjalëkalimit <strong>{{ username }}</strong>",
|
||||
"disable_totp_btn": "Çaktivizo vërtetimin me dy faktorë për këtë përdorues",
|
||||
"disable_totp_title": "Çaktivizo vërtetimin me dy faktorë",
|
||||
"disable_totp_message": "Jeni i sigurt që dëshironi të çaktivizoni vërtetimin me dy faktorë për këtë përdorues?"
|
||||
}
|
||||
},
|
||||
"workflow": {
|
||||
"title": "Konfigurimi i rrjedhës së punës",
|
||||
"add_workflow": "Shto një rrjedhë pune",
|
||||
"name": "Emri",
|
||||
"create_date": "Krijo datë",
|
||||
"edit": {
|
||||
"delete_workflow_title": "Fshi fluksin e punës",
|
||||
"delete_workflow_message": "Dëshiron vërtet ta fshish këtë rrjedhë pune? ",
|
||||
"edit_workflow_title": "Redakto \"{{ name }}\"",
|
||||
"add_workflow_title": "Shto një rrjedhë pune",
|
||||
"name": "Emri",
|
||||
"name_placeholder": "Emri ose përshkrimi i hapit",
|
||||
"drag_help": "Zvarrit dhe lësho për të rirenditur hapin",
|
||||
"type": "Lloji i hapit",
|
||||
"type_approve": "Mirato",
|
||||
"type_validate": "Vërtetoni",
|
||||
"target": "Caktuar për",
|
||||
"target_help": "<strong>Mirato:</strong> Pranoni ose refuzoni rishikimin<br/><strong>Vërteto:</strong> Rishikoni dhe vazhdoni rrjedhën e punës",
|
||||
"add_step": "Shto një hap të rrjedhës së punës",
|
||||
"actions": "Çfarë ndodh më pas?",
|
||||
"remove_action": "Hiq veprimin",
|
||||
"acl_info": "Vetëm përdoruesit dhe grupet e përcaktuara këtu do të mund të fillojnë këtë rrjedhë pune në një dokument"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
"enable_totp": "Aktivizo vërtetimin me dy faktorë",
|
||||
"enable_totp_message": "Sigurohuni që të keni një aplikacion të përputhshëm me TOTP në telefonin tuaj gati për të shtuar një llogari të re",
|
||||
"title": "Autentifikimi me dy faktorë",
|
||||
"message_1": "Autentifikimi me dy faktorë ju lejon të shtoni një shtresë sigurie në tuaj <strong>{{ appName }}</strong> llogari.<br/>Përpara se të aktivizoni këtë veçori, sigurohuni që të keni një aplikacion të pajtueshëm me TOTP në telefonin tuaj:",
|
||||
"message_google_authenticator": "Për Android, iOS dhe Blackberry: <a href=\"https://support.google.com/accounts/answer/1066447\" target=\"_blank\">Google Authenticator</a>",
|
||||
"message_duo_mobile": "Për Android dhe iOS: <a href=\"https://guide.duo.com/third-party-accounts\" target=\"_blank\">Duo Mobile</a>",
|
||||
"message_authenticator": "Për Windows Phone: <a href=\"https://www.microsoft.com/en-US/store/apps/Authenticator/9WZDNCRFJ3RJ\" target=\"_blank\">Vërtetuesi</a>",
|
||||
"message_2": "Këto aplikacione gjenerojnë automatikisht një kod verifikimi që ndryshon pas një periudhe të caktuar kohe.<br/>Do t'ju kërkohet të vendosni këtë kod verifikimi sa herë që identifikoheni <strong>{{ appName }}</strong>.",
|
||||
"secret_key": "Çelësi juaj sekret është: <strong>{{ secret }}</strong>",
|
||||
"secret_key_warning": "Konfiguro aplikacionin tënd TOTP në telefonin tënd me këtë çelës sekret tani, nuk do të mund ta qasesh më vonë.",
|
||||
"totp_enabled_message": "Autentifikimi me dy faktorë është aktivizuar në llogarinë tuaj.<br/>Sa herë që identifikoheni <strong>{{ appName }}</strong>, do t'ju kërkohet një kod verifikimi nga aplikacioni i telefonit tuaj të konfiguruar.<br/>Nëse e humbni telefonin, nuk do të jeni në gjendje të identifikoheni në llogarinë tuaj, por seancat aktive do t'ju lejojnë të rigjeneroni një çelës sekret.",
|
||||
"disable_totp": {
|
||||
"disable_totp": "Çaktivizo vërtetimin me dy faktorë",
|
||||
"message": "Llogaria juaj nuk do të mbrohet më nga vërtetimi me dy faktorë.",
|
||||
"confirm_password": "Konfirmoni fjalëkalimin tuaj",
|
||||
"submit": "Çaktivizo vërtetimin me dy faktorë"
|
||||
},
|
||||
"test_totp": "Ju lutemi shkruani kodin e vërtetimit të shfaqur në telefonin tuaj:",
|
||||
"test_code_success": "Kodi i verifikimit në rregull",
|
||||
"test_code_fail": "Ky kod nuk është i vlefshëm, ju lutemi kontrolloni dy herë nëse telefoni juaj është i konfiguruar siç duhet ose çaktivizoni vërtetimin me dy faktorë"
|
||||
},
|
||||
"group": {
|
||||
"title": "Menaxhimi i grupeve",
|
||||
"add_group": "Shto një grup",
|
||||
"name": "Emri",
|
||||
"edit": {
|
||||
"delete_group_title": "Fshi grupin",
|
||||
"delete_group_message": "Dëshiron vërtet ta fshish këtë grup?",
|
||||
"edit_group_failed_title": "Grupi tashmë ekziston",
|
||||
"edit_group_failed_message": "Ky emër grupi është marrë tashmë nga një grup tjetër",
|
||||
"group_used_title": "Grupi në përdorim",
|
||||
"group_used_message": "Ky grup përdoret në rrjedhën e punës \"{{ name }}\"",
|
||||
"edit_group_title": "Redakto \"{{ name }}\"",
|
||||
"add_group_title": "Shto një grup",
|
||||
"name": "Emri",
|
||||
"parent_group": "Grupi i prindërve",
|
||||
"search_group": "Kërkoni një grup",
|
||||
"members": "Anëtarët",
|
||||
"new_member": "Anëtar i ri",
|
||||
"search_user": "Kërkoni një përdorues"
|
||||
}
|
||||
},
|
||||
"account": {
|
||||
"title": "Llogaria e përdoruesit",
|
||||
"password": "Fjalëkalimi",
|
||||
"password_confirm": "Fjalëkalimi (konfirmo)",
|
||||
"updated": "Llogaria u përditësua me sukses"
|
||||
},
|
||||
"config": {
|
||||
"title_guest_access": "Qasja e mysafirëve",
|
||||
"message_guest_access": "Qasja e mysafirëve është një mënyrë ku çdokush mund të hyjë {{ appName }} pa fjalëkalim.<br/>Ashtu si një përdorues normal, përdoruesi mysafir mund të qaset vetëm në dokumentet e tij dhe ato të aksesueshme përmes lejeve.<br/>",
|
||||
"enable_guest_access": "Aktivizo qasjen e vizitorëve",
|
||||
"disable_guest_access": "Çaktivizo qasjen e vizitorëve",
|
||||
"title_theme": "Personalizimi i temës",
|
||||
"title_general": "Konfigurimi i përgjithshëm",
|
||||
"default_language": "Gjuha e parazgjedhur për dokumentet e reja",
|
||||
"application_name": "Emri i aplikacionit",
|
||||
"main_color": "Ngjyra kryesore",
|
||||
"custom_css": "CSS e personalizuar",
|
||||
"custom_css_placeholder": "CSS e personalizuar për t'u shtuar pas fletës kryesore të stilit",
|
||||
"logo": "Logo (madhësia katrore)",
|
||||
"background_image": "Imazhi i sfondit",
|
||||
"uploading_image": "Po ngarkon imazhin...",
|
||||
"title_smtp": "Konfigurimi i emailit",
|
||||
"smtp_hostname": "Emri i hostit SMTP",
|
||||
"smtp_port": "Porta SMTP",
|
||||
"smtp_from": "E-mail i dërguesit",
|
||||
"smtp_username": "Emri i përdoruesit SMTP",
|
||||
"smtp_password": "Fjalëkalimi SMTP",
|
||||
"smtp_updated": "Konfigurimi SMTP u përditësua me sukses",
|
||||
"webhooks": "Uebhooks",
|
||||
"webhooks_explain": "Webhooks do të thirren kur të ndodhë ngjarja e specifikuar. ",
|
||||
"webhook_event": "Ngjarja",
|
||||
"webhook_url": "URL",
|
||||
"webhook_create_date": "Krijo datë",
|
||||
"webhook_add": "Shto një uebhook"
|
||||
},
|
||||
"metadata": {
|
||||
"title": "Konfigurimi i personalizuar i meta të dhënave",
|
||||
"message": "Këtu mund të shtoni meta të dhëna të personalizuara në dokumentet tuaja si një identifikues i brendshëm ose një datë skadimi. ",
|
||||
"name": "Emri i meta të dhënave",
|
||||
"type": "Lloji i meta të dhënave"
|
||||
},
|
||||
"inbox": {
|
||||
"title": "Skanimi i kutisë hyrëse",
|
||||
"message": "Duke aktivizuar këtë veçori, sistemi do të skanojë kutinë hyrëse të specifikuar çdo minutë <strong>i palexuar</strong> emailet dhe i importoni automatikisht.<br/>Pas importimit të një emaili, ai do të shënohet si i lexuar.<br/>Cilësimet e konfigurimit për <a href=\"https://support.google.com/mail/answer/7126229?hl=en\" target=\"_blank\">Gmail</a>, <a href=\"https://support.office.com/en-us/article/pop-imap-and-smtp-settings-for-outlook-com-d088b986-291d-42b8-9564-9c414e2aa040\" target=\"_blank\">Outlook.com</a>, <a href=\"https://help.yahoo.com/kb/SLN4075.html\" target=\"_blank\">Yahoo</a>.",
|
||||
"enabled": "Aktivizo skanimin e kutisë hyrëse",
|
||||
"hostname": "Emri i hostit IMAP",
|
||||
"port": "Porta IMAP (143 ose 993)",
|
||||
"starttls": "Aktivizo STARTTLS",
|
||||
"username": "Emri i përdoruesit IMAP",
|
||||
"password": "Fjalëkalimi IMAP",
|
||||
"folder": "Dosja IMAP",
|
||||
"tag": "Etiketa u shtua në dokumentet e importuara",
|
||||
"test": "Testoni parametrat",
|
||||
"last_sync": "Sinkronizimi i fundit: {{ data.date | date: 'medium' }}, {{ data.count }} mesazh{{ data.count > 1 ? 's' : '' }} të importuara",
|
||||
"test_success": "Lidhja me kutinë hyrëse është e suksesshme ({{ count }} <strong>i palexuar</strong> mesazh{{ count > 1 ? 's' : '' }})",
|
||||
"test_fail": "Ndodhi një gabim gjatë lidhjes me kutinë hyrëse, ju lutemi kontrolloni parametrat",
|
||||
"saved": "Konfigurimi IMAP u ruajt me sukses",
|
||||
"autoTagsEnabled": "Shtoni automatikisht etiketat nga rreshti i subjektit të shënuar me",
|
||||
"deleteImported": "Fshi mesazhin nga kutia postare pas importimit"
|
||||
},
|
||||
"monitoring": {
|
||||
"background_tasks": "Detyrat e sfondit",
|
||||
"queued_tasks": "Aktualisht ka {{ count }} detyrat në radhë.",
|
||||
"queued_tasks_explain": "Përpunimi i skedarëve, krijimi i miniaturave, përditësimi i indeksit, njohja optike e karaktereve janë detyra në sfond. ",
|
||||
"server_logs": "Regjistrat e serverit",
|
||||
"log_date": "Data",
|
||||
"log_tag": "Etiketë",
|
||||
"log_message": "Mesazh",
|
||||
"indexing": "Indeksimi",
|
||||
"indexing_info": "Nëse vëreni mospërputhje në rezultatet e kërkimit, mund të provoni të bëni një riindeksim të plotë. ",
|
||||
"start_reindexing": "Filloni riindeksimin e plotë",
|
||||
"reindexing_started": "Ri-indeksimi filloi, ju lutemi prisni derisa të mos ketë më detyra në sfond."
|
||||
},
|
||||
"session": {
|
||||
"title": "Seancat e hapura",
|
||||
"created_date": "Data e krijimit",
|
||||
"last_connection_date": "Data e fundit e lidhjes",
|
||||
"user_agent": "Nga",
|
||||
"current": "Aktuale",
|
||||
"current_session": "Ky është sesioni aktual",
|
||||
"clear_message": "Të gjitha pajisjet e tjera të lidhura me këtë llogari do të shkëputen",
|
||||
"clear": "Pastro të gjitha seancat e tjera"
|
||||
},
|
||||
"vocabulary": {
|
||||
"title": "Shënimet e fjalorit",
|
||||
"choose_vocabulary": "Zgjidhni një fjalor për të redaktuar",
|
||||
"type": "Lloji",
|
||||
"coverage": "Mbulimi",
|
||||
"rights": "Të drejtat",
|
||||
"value": "Vlera",
|
||||
"order": "Rendit",
|
||||
"new_entry": "Hyrje e re"
|
||||
},
|
||||
"fileimporter": {
|
||||
"title": "Importuesi i skedarëve në masë",
|
||||
"advanced_users": "Për përdoruesit e avancuar!",
|
||||
"need_intro": "Nëse ju duhet:",
|
||||
"need_1": "Importoni një direktori skedarësh menjëherë",
|
||||
"need_2": "Skanoni një drejtori për skedarë të rinj dhe importojini ato",
|
||||
"line_1": "Shkoni në <a href=\"https://github.com/sismics/docs/releases\">sismics/docs/releases</a> dhe shkarkoni mjetin e importuesit të skedarëve për sistemin tuaj.",
|
||||
"line_2": "Ndiq <a href=\"https://github.com/sismics/docs/tree/master/docs-importer\">udhëzime këtu</a> për të përdorur këtë mjet.",
|
||||
"line_3": "Skedarët tuaj do të importohen në dokumente sipas konfigurimit të importuesit të skedarëve.",
|
||||
"download": "Shkarko",
|
||||
"instructions": "Udhëzimet"
|
||||
}
|
||||
},
|
||||
"feedback": {
|
||||
"title": "Na jepni një koment",
|
||||
"message": "Ndonjë sugjerim apo pyetje në lidhje me Teedy? ",
|
||||
"sent_title": "Komentet u dërguan",
|
||||
"sent_message": "Faleminderit për komentin tuaj! "
|
||||
},
|
||||
"import": {
|
||||
"title": "Importimi",
|
||||
"error_quota": "U arrit kufiri i kuotës, kontaktoni administratorin tuaj për të rritur kuotën tuaj",
|
||||
"error_general": "Ndodhi një gabim gjatë përpjekjes për të importuar skedarin tuaj, ju lutemi sigurohuni që ai është një skedar i vlefshëm EML"
|
||||
},
|
||||
"app_share": {
|
||||
"403": {
|
||||
"title": "I pa autorizuar",
|
||||
"message": "Dokumenti që po përpiqeni të shikoni nuk ndahet më"
|
||||
},
|
||||
"main": "Kërkoni një lidhje të përbashkët të dokumentit për të hyrë në të"
|
||||
},
|
||||
"directive": {
|
||||
"acledit": {
|
||||
"acl_target": "Për",
|
||||
"acl_permission": "Leja",
|
||||
"add_permission": "Shto një leje",
|
||||
"search_user_group": "Kërkoni një përdorues ose grup"
|
||||
},
|
||||
"auditlog": {
|
||||
"log_created": "krijuar",
|
||||
"log_updated": "përditësuar",
|
||||
"log_deleted": "fshihet",
|
||||
"Acl": "ACL",
|
||||
"Comment": "Komentoni",
|
||||
"Document": "Dokumenti",
|
||||
"File": "Skedari",
|
||||
"Group": "Grupi",
|
||||
"Route": "Rrjedha e punës",
|
||||
"RouteModel": "Modeli i rrjedhës së punës",
|
||||
"Tag": "Etiketë",
|
||||
"User": "Përdoruesi",
|
||||
"Webhook": "Uebhook"
|
||||
},
|
||||
"selectrelation": {
|
||||
"typeahead": "Shkruani një titull dokumenti"
|
||||
},
|
||||
"selecttag": {
|
||||
"typeahead": "Shkruani një etiketë"
|
||||
},
|
||||
"datepicker": {
|
||||
"current": "Sot",
|
||||
"clear": "Qartë",
|
||||
"close": "U krye"
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
"filesize": {
|
||||
"mb": "MB",
|
||||
"kb": "kB"
|
||||
}
|
||||
},
|
||||
"acl": {
|
||||
"READ": "Mund të lexojë",
|
||||
"READWRITE": "Mund të shkruajë",
|
||||
"WRITE": "Mund të shkruajë",
|
||||
"USER": "Përdoruesi",
|
||||
"GROUP": "Grupi",
|
||||
"SHARE": "Të përbashkëta"
|
||||
},
|
||||
"workflow_type": {
|
||||
"VALIDATE": "Vleresimi",
|
||||
"APPROVE": "Miratimi"
|
||||
},
|
||||
"workflow_transition": {
|
||||
"APPROVED": "Miratuar",
|
||||
"REJECTED": "Refuzuar",
|
||||
"VALIDATED": "E vërtetuar"
|
||||
},
|
||||
"validation": {
|
||||
"required": "E detyrueshme",
|
||||
"too_short": "Shumë e shkurtër",
|
||||
"too_long": "Shume gjate",
|
||||
"email": "Duhet të jetë një e-mail i vlefshëm",
|
||||
"password_confirm": "Fjalëkalimi dhe konfirmimi i fjalëkalimit duhet të përputhen",
|
||||
"number": "Numri i kërkuar",
|
||||
"no_space": "Hapësirat dhe dy pikat nuk lejohen",
|
||||
"alphanumeric": "Lejohen vetëm shkronja dhe numra"
|
||||
},
|
||||
"action_type": {
|
||||
"ADD_TAG": "Shto një etiketë",
|
||||
"REMOVE_TAG": "Hiq një etiketë",
|
||||
"PROCESS_FILES": "Përpunoni skedarët"
|
||||
},
|
||||
"pagination": {
|
||||
"previous": "E mëparshme",
|
||||
"next": "Tjetra",
|
||||
"first": "Së pari",
|
||||
"last": "E fundit"
|
||||
},
|
||||
"onboarding": {
|
||||
"step1": {
|
||||
"title": "Hera e parë?",
|
||||
"description": "Nëse është hera juaj e parë në Teedy, klikoni butonin Next, përndryshe mos ngurroni të më mbyllni."
|
||||
},
|
||||
"step2": {
|
||||
"title": "Dokumentet",
|
||||
"description": "Teedy është i organizuar në dokumente dhe çdo dokument përmban skedarë të shumtë."
|
||||
},
|
||||
"step3": {
|
||||
"title": "Skedarët",
|
||||
"description": "Mund të shtoni skedarë pas krijimit të një dokumenti ose përpara se të përdorni këtë zonë të ngarkimit të shpejtë."
|
||||
},
|
||||
"step4": {
|
||||
"title": "Kërko",
|
||||
"description": "Kjo është mënyra kryesore për të gjetur përsëri dokumentet tuaja. "
|
||||
},
|
||||
"step5": {
|
||||
"title": "Etiketa",
|
||||
"description": "Dokumentet mund të organizohen në etiketa (të cilat janë si super-dosje). "
|
||||
}
|
||||
},
|
||||
"yes": "po",
|
||||
"no": "Nr",
|
||||
"ok": "Në rregull",
|
||||
"cancel": "Anulo",
|
||||
"share": "Shpërndaje",
|
||||
"unshare": "Shpërndaje",
|
||||
"close": "Mbylle",
|
||||
"add": "Shtoni",
|
||||
"open": "Hapur",
|
||||
"see": "Shiko",
|
||||
"save": "Ruaj",
|
||||
"export": "Eksporto",
|
||||
"edit": "Redakto",
|
||||
"delete": "Fshije",
|
||||
"rename": "Riemërto",
|
||||
"download": "Shkarko",
|
||||
"loading": "Po ngarkohet...",
|
||||
"send": "Dërgo",
|
||||
"enabled": "Aktivizuar",
|
||||
"disabled": "I paaftë"
|
||||
}
|
||||
@@ -77,12 +77,13 @@
|
||||
<span class="fas fa-spin fa-circle-notch"></span>
|
||||
</div>
|
||||
|
||||
<a class="file-thumbnail" ng-click="openFile(file, $event)">
|
||||
<img ng-src="../api/file/{{ file.id }}/data?size=thumb" uib-tooltip="{{ file.mimetype }} | {{ file.size | filesize }}" tooltip-placement="top" />
|
||||
<a class="file-thumbnail" ng-click="openFile(file, $event)"
|
||||
uib-tooltip="{{ file.mimetype }} | {{ file.size | filesize }}" tooltip-placement="top">
|
||||
<img ng-src="../api/file/{{ file.id }}/data?size=thumb" />
|
||||
</a>
|
||||
|
||||
<div class="file-info">
|
||||
<div></div><!-- Actually useful -->
|
||||
<div></div>
|
||||
<div class="v-align file-name" ng-if="file.name" title="{{ file.name }} (v{{ file.version + 1 }}.0)">{{ file.name }}</div>
|
||||
<div class="v-align" ng-show="document.writable">
|
||||
<div uib-dropdown>
|
||||
|
||||
@@ -31,6 +31,17 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<h2>
|
||||
<span translate="settings.config.title_ocr"></span>
|
||||
<span class="label" ng-class="{ 'label-success': app.ocr_enabled, 'label-danger': !app.ocr_enabled }">
|
||||
{{ app.ocr_enabled ? 'enabled' : 'disabled' | translate }}
|
||||
</h2>
|
||||
<p translate="settings.config.message_ocr" translate-values="{ appName: appName }"></p>
|
||||
<div ng-if="app">
|
||||
<button ng-if="!app.ocr_enabled" class="btn btn-primary" ng-click="changeOcrEnabled(true)">{{ 'settings.config.enable_ocr' | translate }}</button>
|
||||
<button ng-if="app.ocr_enabled" class="btn btn-danger" ng-click="changeOcrEnabled(false)">{{ 'settings.config.disable_ocr' | translate }}</button>
|
||||
</div>
|
||||
|
||||
<h2 translate="settings.config.title_theme"></h2>
|
||||
<form class="form-horizontal" name="editColorForm" novalidate>
|
||||
<div class="form-group">
|
||||
@@ -196,4 +207,4 @@
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p ng-bind-html="document.description"></p>
|
||||
<p class="well-sm" ng-bind-html="document.description"></p>
|
||||
<dl class="dl-horizontal">
|
||||
<dt ng-if="document.subject">{{ 'document.subject' | translate }}</dt>
|
||||
<dd ng-if="document.subject">{{ document.subject }}</dd>
|
||||
@@ -63,18 +63,80 @@
|
||||
</span>
|
||||
</span>
|
||||
</dd>
|
||||
<dt ng-if="false" ng-repeat-start="meta in document.metadata"></dt>
|
||||
<dt ng-if="meta.value != null">{{ meta.name }}</dt>
|
||||
<dd ng-if="meta.value != null && (meta.type == 'STRING' || meta.type == 'INTEGER' || meta.type == 'FLOAT')">{{ meta.value }}</dd>
|
||||
<dd ng-if="meta.value != null && meta.type == 'DATE'">{{ meta.value | date: dateFormat }}</dd>
|
||||
<dd ng-if="meta.value != null && meta.type == 'BOOLEAN'">{{ meta.value ? 'yes' : 'no' | translate }}</dd>
|
||||
<dd ng-if="false" ng-repeat-end></dd>
|
||||
</dl>
|
||||
|
||||
<!-- Display mode (list or grid) -->
|
||||
<div class="btn-group mt-10 mb-10 pull-right">
|
||||
<span class="btn btn-default" ng-class="{ active: displayMode == 'list' }"
|
||||
uib-tooltip="{{ 'document.view.content.display_mode_list' | translate }}"
|
||||
tooltip-append-to-body="true"
|
||||
ng-click="displayMode = 'list'">
|
||||
<span class="fas fa-list"></span>
|
||||
</span>
|
||||
<span class="btn btn-default" ng-class="{ active: displayMode == 'grid' }"
|
||||
uib-tooltip="{{ 'document.view.content.display_mode_grid' | translate }}"
|
||||
tooltip-append-to-body="true"
|
||||
ng-click="displayMode = 'grid'">
|
||||
<span class="fas fa-th"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<!-- List of files -->
|
||||
<div class="row" ng-show="files.length > 0">
|
||||
<div class="col-xs-6 col-sm-4 col-md-3 col-lg-2 text-center" ng-repeat="file in files">
|
||||
<div class="thumbnail">
|
||||
<a ng-click="openFile(file)">
|
||||
<img ng-src="../api/file/{{ file.id }}/data?size=thumb&share={{ $stateParams.shareId }}"
|
||||
uib-tooltip="{{ file.mimetype }} | {{ file.size | filesize }}" tooltip-placement="top" />
|
||||
</a>
|
||||
<!-- Grid view -->
|
||||
<div ng-show="displayMode == 'grid'">
|
||||
<div class="col-xs-6 col-sm-4 col-md-4 col-lg-3 text-center" ng-repeat="file in files">
|
||||
<div class="thumbnail">
|
||||
<a class="file-thumbnail" ng-click="openFile(file)"
|
||||
uib-tooltip="{{ file.mimetype }} | {{ file.size | filesize }}" tooltip-placement="top">
|
||||
<img ng-src="../api/file/{{ file.id }}/data?size=thumb&share={{ $stateParams.shareId }}" />
|
||||
</a>
|
||||
|
||||
<div class="file-info">
|
||||
<div></div>
|
||||
<div class="v-align file-name" ng-if="file.name" title="{{ file.name }} (v{{ file.version + 1 }}.0)">{{ file.name }}</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- List view -->
|
||||
<table ng-show="displayMode == 'list'" class="table table-hover table-files">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="3%"></th>
|
||||
<th>Filename</th>
|
||||
<th>Type</th>
|
||||
<th>Size</th>
|
||||
<th width="10%">Version</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="file in files">
|
||||
<td class="pointer" ng-click="openFile(file)">
|
||||
<div class="thumbnail-list">
|
||||
<img ng-src="../api/file/{{ file.id }}/data?size=thumb&share={{ $stateParams.shareId }}" />
|
||||
</div>
|
||||
</td>
|
||||
<td class="pointer" ng-click="openFile(file)">
|
||||
{{ file.name }}
|
||||
</td>
|
||||
<td>{{ file.mimetype }}</td>
|
||||
<td>{{ file.size | filesize }}</td>
|
||||
<td>v{{ file.version + 1 }}.0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div ui-view="file"></div>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="renderer" content="webkit" />
|
||||
<link rel="shortcut icon" href="favicon.png" />
|
||||
<link rel="shortcut icon" href="../api/theme/image/logo" />
|
||||
<!-- ref:css style/style.min.css?@build.date@ -->
|
||||
<link rel="stylesheet" href="style/bootstrap.css" type="text/css" />
|
||||
<link rel="stylesheet" href="style/fontawesome.css" type="text/css" />
|
||||
@@ -102,4 +102,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=28
|
||||
db.version=31
|
||||
|
||||
12
docs-web/src/prod/resources/log4j.properties
Normal file
12
docs-web/src/prod/resources/log4j.properties
Normal file
@@ -0,0 +1,12 @@
|
||||
log4j.rootCategory=WARN, CONSOLE, MEMORY
|
||||
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.CONSOLE.layout.ConversionPattern=%d{DATE} %p %l %m %n
|
||||
log4j.appender.MEMORY=com.sismics.util.log4j.MemoryAppender
|
||||
log4j.appender.MEMORY.size=1000
|
||||
|
||||
log4j.logger.com.sismics=INFO
|
||||
log4j.logger.org.apache.pdfbox=ERROR
|
||||
log4j.logger.org.glassfish.jersey.servlet.WebComponent=ERROR
|
||||
log4j.logger.org.apache.directory=ERROR
|
||||
log4j.logger.org.odftoolkit=ERROR
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%date [%t] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<appender name="MEMORY" class="com.sismics.util.logback.MemoryAppender">
|
||||
<size>1000</size>
|
||||
</appender>
|
||||
<logger name="org.apache.directory" level="ERROR"/>
|
||||
<logger name="com.sismics" level="INFO"/>
|
||||
<logger name="org.apache.pdfbox" level="ERROR"/>
|
||||
<logger name="org.glassfish.jersey.servlet.WebComponent" level="ERROR"/>
|
||||
<logger name="org.odftoolkit" level="ERROR"/>
|
||||
<root level="WARN">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="MEMORY"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.sismics.docs.rest;
|
||||
|
||||
import com.sismics.docs.core.dao.UserDao;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
import com.sismics.util.jpa.EMF;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.EntityTransaction;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
public abstract class BaseTransactionalTest {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// Initialize the entity manager
|
||||
EntityManager em = EMF.get().createEntityManager();
|
||||
ThreadLocalContext context = ThreadLocalContext.get();
|
||||
context.setEntityManager(em);
|
||||
em.getTransaction().begin();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
ThreadLocalContext.get().getEntityManager().getTransaction().rollback();
|
||||
}
|
||||
|
||||
protected User createUser(String userName) throws Exception {
|
||||
UserDao userDao = new UserDao();
|
||||
User user = new User();
|
||||
user.setUsername(userName);
|
||||
user.setPassword("12345678");
|
||||
user.setEmail("toto@docs.com");
|
||||
user.setRoleId("admin");
|
||||
user.setStorageQuota(100_000L);
|
||||
userDao.create(user, userName);
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,35 +1,52 @@
|
||||
package com.sismics.docs.rest;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import com.google.common.io.Resources;
|
||||
import com.icegreen.greenmail.util.GreenMail;
|
||||
import com.icegreen.greenmail.util.GreenMailUtil;
|
||||
import com.icegreen.greenmail.util.ServerSetup;
|
||||
import com.sismics.docs.core.model.context.AppContext;
|
||||
import com.sismics.util.filter.TokenBasedSecurityFilter;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import jakarta.json.JsonArray;
|
||||
import jakarta.json.JsonObject;
|
||||
import jakarta.ws.rs.client.Entity;
|
||||
import jakarta.ws.rs.core.Form;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.Response.Status;
|
||||
import org.apache.directory.api.ldap.model.name.Dn;
|
||||
import org.apache.directory.server.core.api.DirectoryService;
|
||||
import org.apache.directory.server.core.api.partition.Partition;
|
||||
import org.apache.directory.server.core.factory.DefaultDirectoryServiceFactory;
|
||||
import org.apache.directory.server.core.factory.DirectoryServiceFactory;
|
||||
import org.apache.directory.server.core.partition.impl.avl.AvlPartition;
|
||||
import org.apache.directory.server.ldap.LdapServer;
|
||||
import org.apache.directory.server.protocol.shared.store.LdifFileLoader;
|
||||
import org.apache.directory.server.protocol.shared.transport.TcpTransport;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
/**
|
||||
* Test the app resource.
|
||||
*
|
||||
*
|
||||
* @author jtremeaux
|
||||
*/
|
||||
public class TestAppResource extends BaseJerseyTest {
|
||||
/**
|
||||
* Test the API resource.
|
||||
*/
|
||||
|
||||
// Record if config has been changed by previous test runs
|
||||
private static boolean configInboxChanged = false;
|
||||
private static boolean configSmtpChanged = false;
|
||||
private static boolean configLdapChanged = false;
|
||||
|
||||
@Test
|
||||
public void testAppResource() {
|
||||
// Login admin
|
||||
String adminToken = clientUtil.login("admin", "admin", false);
|
||||
|
||||
String adminToken = adminToken();
|
||||
|
||||
// Check the application info
|
||||
JsonObject json = target().path("/app").request()
|
||||
.get(JsonObject.class);
|
||||
@@ -41,6 +58,7 @@ public class TestAppResource extends BaseJerseyTest {
|
||||
Assert.assertTrue(totalMemory > 0 && totalMemory > freeMemory);
|
||||
Assert.assertEquals(0, json.getJsonNumber("queued_tasks").intValue());
|
||||
Assert.assertFalse(json.getBoolean("guest_login"));
|
||||
Assert.assertFalse(json.getBoolean("ocr_enabled"));
|
||||
Assert.assertEquals("eng", json.getString("default_language"));
|
||||
Assert.assertTrue(json.containsKey("global_storage_current"));
|
||||
Assert.assertTrue(json.getJsonNumber("active_user_count").longValue() > 0);
|
||||
@@ -50,13 +68,13 @@ public class TestAppResource extends BaseJerseyTest {
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.post(Entity.form(new Form()));
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
|
||||
// Clean storage
|
||||
response = target().path("/app/batch/clean_storage").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.post(Entity.form(new Form()));
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
|
||||
// Change the default language
|
||||
response = target().path("/app/config").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
@@ -86,8 +104,8 @@ public class TestAppResource extends BaseJerseyTest {
|
||||
@Test
|
||||
public void testLogResource() {
|
||||
// Login admin
|
||||
String adminToken = clientUtil.login("admin", "admin", false);
|
||||
|
||||
String adminToken = adminToken();
|
||||
|
||||
// Check the logs (page 1)
|
||||
JsonObject json = target().path("/app/log")
|
||||
.queryParam("level", "DEBUG")
|
||||
@@ -99,7 +117,7 @@ public class TestAppResource extends BaseJerseyTest {
|
||||
Long date1 = logs.getJsonObject(0).getJsonNumber("date").longValue();
|
||||
Long date2 = logs.getJsonObject(9).getJsonNumber("date").longValue();
|
||||
Assert.assertTrue(date1 >= date2);
|
||||
|
||||
|
||||
// Check the logs (page 2)
|
||||
json = target().path("/app/log")
|
||||
.queryParam("offset", "10")
|
||||
@@ -120,7 +138,7 @@ public class TestAppResource extends BaseJerseyTest {
|
||||
@Test
|
||||
public void testGuestLogin() {
|
||||
// Login admin
|
||||
String adminToken = clientUtil.login("admin", "admin", false);
|
||||
String adminToken = adminToken();
|
||||
|
||||
// Try to login as guest
|
||||
Response response = target().path("/user/login").request()
|
||||
@@ -177,6 +195,43 @@ public class TestAppResource extends BaseJerseyTest {
|
||||
target().path("/document/list").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, guestToken)
|
||||
.get(JsonObject.class);
|
||||
|
||||
// Disable guest login (clean up state)
|
||||
target().path("/app/guest_login").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.post(Entity.form(new Form()
|
||||
.param("enabled", "false")), JsonObject.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the ocr setting
|
||||
*/
|
||||
@Test
|
||||
public void testOcrSetting() {
|
||||
// Login admin
|
||||
String adminToken = adminToken();
|
||||
|
||||
// Get OCR configuration
|
||||
JsonObject json = target().path("/app/ocr").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.get(JsonObject.class);
|
||||
if (!configInboxChanged) {
|
||||
Assert.assertFalse(json.getBoolean("enabled"));
|
||||
}
|
||||
|
||||
// Change OCR configuration
|
||||
target().path("/app/ocr").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.post(Entity.form(new Form()
|
||||
.param("enabled", "true")
|
||||
), JsonObject.class);
|
||||
configInboxChanged = true;
|
||||
|
||||
// Get OCR configuration
|
||||
json = target().path("/app/ocr").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.get(JsonObject.class);
|
||||
Assert.assertTrue(json.getBoolean("enabled"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,17 +240,19 @@ public class TestAppResource extends BaseJerseyTest {
|
||||
@Test
|
||||
public void testSmtpConfiguration() {
|
||||
// Login admin
|
||||
String adminToken = clientUtil.login("admin", "admin", false);
|
||||
String adminToken = adminToken();
|
||||
|
||||
// Get SMTP configuration
|
||||
JsonObject json = target().path("/app/config_smtp").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.get(JsonObject.class);
|
||||
Assert.assertTrue(json.isNull("hostname"));
|
||||
Assert.assertTrue(json.isNull("port"));
|
||||
Assert.assertTrue(json.isNull("username"));
|
||||
Assert.assertTrue(json.isNull("password"));
|
||||
Assert.assertTrue(json.isNull("from"));
|
||||
if (!configSmtpChanged) {
|
||||
Assert.assertTrue(json.isNull("hostname"));
|
||||
Assert.assertTrue(json.isNull("port"));
|
||||
Assert.assertTrue(json.isNull("username"));
|
||||
Assert.assertTrue(json.isNull("password"));
|
||||
Assert.assertTrue(json.isNull("from"));
|
||||
}
|
||||
|
||||
// Change SMTP configuration
|
||||
target().path("/app/config_smtp").request()
|
||||
@@ -206,6 +263,7 @@ public class TestAppResource extends BaseJerseyTest {
|
||||
.param("username", "sismics")
|
||||
.param("from", "contact@sismics.com")
|
||||
), JsonObject.class);
|
||||
configSmtpChanged = true;
|
||||
|
||||
// Get SMTP configuration
|
||||
json = target().path("/app/config_smtp").request()
|
||||
@@ -224,7 +282,7 @@ public class TestAppResource extends BaseJerseyTest {
|
||||
@Test
|
||||
public void testInbox() {
|
||||
// Login admin
|
||||
String adminToken = clientUtil.login("admin", "admin", false);
|
||||
String adminToken = adminToken();
|
||||
|
||||
// Create a tag
|
||||
JsonObject json = target().path("/tag").request()
|
||||
@@ -238,23 +296,26 @@ public class TestAppResource extends BaseJerseyTest {
|
||||
json = target().path("/app/config_inbox").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.get(JsonObject.class);
|
||||
Assert.assertFalse(json.getBoolean("enabled"));
|
||||
Assert.assertEquals("", json.getString("hostname"));
|
||||
Assert.assertEquals(993, json.getJsonNumber("port").intValue());
|
||||
Assert.assertEquals("", json.getString("username"));
|
||||
Assert.assertEquals("", json.getString("password"));
|
||||
Assert.assertEquals("INBOX", json.getString("folder"));
|
||||
Assert.assertEquals("", json.getString("tag"));
|
||||
JsonObject lastSync = json.getJsonObject("last_sync");
|
||||
Assert.assertTrue(lastSync.isNull("date"));
|
||||
Assert.assertTrue(lastSync.isNull("error"));
|
||||
Assert.assertEquals(0, lastSync.getJsonNumber("count").intValue());
|
||||
if (!configInboxChanged) {
|
||||
Assert.assertFalse(json.getBoolean("enabled"));
|
||||
Assert.assertEquals("", json.getString("hostname"));
|
||||
Assert.assertEquals(993, json.getJsonNumber("port").intValue());
|
||||
Assert.assertEquals("", json.getString("username"));
|
||||
Assert.assertEquals("", json.getString("password"));
|
||||
Assert.assertEquals("INBOX", json.getString("folder"));
|
||||
Assert.assertEquals("", json.getString("tag"));
|
||||
Assert.assertTrue(lastSync.isNull("date"));
|
||||
Assert.assertTrue(lastSync.isNull("error"));
|
||||
Assert.assertEquals(0, lastSync.getJsonNumber("count").intValue());
|
||||
}
|
||||
|
||||
// Change inbox configuration
|
||||
target().path("/app/config_inbox").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.post(Entity.form(new Form()
|
||||
.param("enabled", "true")
|
||||
.param("starttls", "false")
|
||||
.param("autoTagsEnabled", "false")
|
||||
.param("deleteImported", "false")
|
||||
.param("hostname", "localhost")
|
||||
@@ -264,6 +325,7 @@ public class TestAppResource extends BaseJerseyTest {
|
||||
.param("folder", "INBOX")
|
||||
.param("tag", tagInboxId)
|
||||
), JsonObject.class);
|
||||
configInboxChanged = true;
|
||||
|
||||
// Get inbox configuration
|
||||
json = target().path("/app/config_inbox").request()
|
||||
@@ -340,89 +402,93 @@ public class TestAppResource extends BaseJerseyTest {
|
||||
*/
|
||||
@Test
|
||||
public void testLdapAuthentication() throws Exception {
|
||||
// // Start LDAP server
|
||||
// final DirectoryServiceFactory factory = new DefaultDirectoryServiceFactory();
|
||||
// factory.init("Test");
|
||||
//
|
||||
// final DirectoryService directoryService = factory.getDirectoryService();
|
||||
// directoryService.getChangeLog().setEnabled(false);
|
||||
// directoryService.setShutdownHookEnabled(true);
|
||||
//
|
||||
// final Partition partition = new AvlPartition(directoryService.getSchemaManager());
|
||||
// partition.setId("Test");
|
||||
// partition.setSuffixDn(new Dn(directoryService.getSchemaManager(), "o=TEST"));
|
||||
// partition.initialize();
|
||||
// directoryService.addPartition(partition);
|
||||
//
|
||||
// final LdapServer ldapServer = new LdapServer();
|
||||
// ldapServer.setTransports(new TcpTransport("localhost", 11389));
|
||||
// ldapServer.setDirectoryService(directoryService);
|
||||
//
|
||||
// directoryService.startup();
|
||||
// ldapServer.start();
|
||||
//
|
||||
// // Load test data in LDAP
|
||||
// new LdifFileLoader(directoryService.getAdminSession(), new File(Resources.getResource("test.ldif").getFile()), null).execute();
|
||||
//
|
||||
// // Login admin
|
||||
// String adminToken = clientUtil.login("admin", "admin", false);
|
||||
//
|
||||
// // Get the LDAP configuration
|
||||
// JsonObject json = target().path("/app/config_ldap").request()
|
||||
// .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
// .get(JsonObject.class);
|
||||
// Assert.assertFalse(json.getBoolean("enabled"));
|
||||
//
|
||||
// // Change LDAP configuration
|
||||
// target().path("/app/config_ldap").request()
|
||||
// .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
// .post(Entity.form(new Form()
|
||||
// .param("enabled", "true")
|
||||
// .param("host", "localhost")
|
||||
// .param("port", "11389")
|
||||
// .param("admin_dn", "uid=admin,ou=system")
|
||||
// .param("admin_password", "secret")
|
||||
// .param("base_dn", "o=TEST")
|
||||
// .param("filter", "(&(objectclass=inetOrgPerson)(uid=USERNAME))")
|
||||
// .param("default_email", "devnull@teedy.io")
|
||||
// .param("default_storage", "100000000")
|
||||
// ), JsonObject.class);
|
||||
//
|
||||
// // Get the LDAP configuration
|
||||
// json = target().path("/app/config_ldap").request()
|
||||
// .cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
// .get(JsonObject.class);
|
||||
// Assert.assertTrue(json.getBoolean("enabled"));
|
||||
// Assert.assertEquals("localhost", json.getString("host"));
|
||||
// Assert.assertEquals(11389, json.getJsonNumber("port").intValue());
|
||||
// Assert.assertEquals("uid=admin,ou=system", json.getString("admin_dn"));
|
||||
// Assert.assertEquals("secret", json.getString("admin_password"));
|
||||
// Assert.assertEquals("o=TEST", json.getString("base_dn"));
|
||||
// Assert.assertEquals("(&(objectclass=inetOrgPerson)(uid=USERNAME))", json.getString("filter"));
|
||||
// Assert.assertEquals("devnull@teedy.io", json.getString("default_email"));
|
||||
// Assert.assertEquals(100000000L, json.getJsonNumber("default_storage").longValue());
|
||||
//
|
||||
// // Login with a LDAP user
|
||||
// String ldapTopen = clientUtil.login("ldap1", "secret", false);
|
||||
//
|
||||
// // Check user informations
|
||||
// json = target().path("/user").request()
|
||||
// .cookie(TokenBasedSecurityFilter.COOKIE_NAME, ldapTopen)
|
||||
// .get(JsonObject.class);
|
||||
// Assert.assertEquals("ldap1@teedy.io", json.getString("email"));
|
||||
//
|
||||
// // List all documents
|
||||
// json = target().path("/document/list")
|
||||
// .queryParam("sort_column", 3)
|
||||
// .queryParam("asc", true)
|
||||
// .request()
|
||||
// .cookie(TokenBasedSecurityFilter.COOKIE_NAME, ldapTopen)
|
||||
// .get(JsonObject.class);
|
||||
// JsonArray documents = json.getJsonArray("documents");
|
||||
// Assert.assertEquals(0, documents.size());
|
||||
//
|
||||
// // Stop LDAP server
|
||||
// ldapServer.stop();
|
||||
// directoryService.shutdown();
|
||||
// Start LDAP server
|
||||
final DirectoryServiceFactory factory = new DefaultDirectoryServiceFactory();
|
||||
factory.init("Test");
|
||||
|
||||
final DirectoryService directoryService = factory.getDirectoryService();
|
||||
directoryService.getChangeLog().setEnabled(false);
|
||||
directoryService.setShutdownHookEnabled(true);
|
||||
|
||||
final Partition partition = new AvlPartition(directoryService.getSchemaManager());
|
||||
partition.setId("Test");
|
||||
partition.setSuffixDn(new Dn(directoryService.getSchemaManager(), "o=TEST"));
|
||||
partition.initialize();
|
||||
directoryService.addPartition(partition);
|
||||
|
||||
final LdapServer ldapServer = new LdapServer();
|
||||
ldapServer.setTransports(new TcpTransport("localhost", 11389));
|
||||
ldapServer.setDirectoryService(directoryService);
|
||||
|
||||
directoryService.startup();
|
||||
ldapServer.start();
|
||||
|
||||
// Load test data in LDAP
|
||||
new LdifFileLoader(directoryService.getAdminSession(), new File(Resources.getResource("test.ldif").getFile()), null).execute();
|
||||
|
||||
// Login admin
|
||||
String adminToken = adminToken();
|
||||
|
||||
// Get the LDAP configuration
|
||||
JsonObject json = target().path("/app/config_ldap").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.get(JsonObject.class);
|
||||
if (!configLdapChanged) {
|
||||
Assert.assertFalse(json.getBoolean("enabled"));
|
||||
}
|
||||
|
||||
// Change LDAP configuration
|
||||
target().path("/app/config_ldap").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.post(Entity.form(new Form()
|
||||
.param("enabled", "true")
|
||||
.param("host", "localhost")
|
||||
.param("port", "11389")
|
||||
.param("usessl", "false")
|
||||
.param("admin_dn", "uid=admin,ou=system")
|
||||
.param("admin_password", "secret")
|
||||
.param("base_dn", "o=TEST")
|
||||
.param("filter", "(&(objectclass=inetOrgPerson)(uid=USERNAME))")
|
||||
.param("default_email", "devnull@teedy.io")
|
||||
.param("default_storage", "100000000")
|
||||
), JsonObject.class);
|
||||
configLdapChanged = true;
|
||||
|
||||
// Get the LDAP configuration
|
||||
json = target().path("/app/config_ldap").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.get(JsonObject.class);
|
||||
Assert.assertTrue(json.getBoolean("enabled"));
|
||||
Assert.assertEquals("localhost", json.getString("host"));
|
||||
Assert.assertEquals(11389, json.getJsonNumber("port").intValue());
|
||||
Assert.assertEquals("uid=admin,ou=system", json.getString("admin_dn"));
|
||||
Assert.assertEquals("secret", json.getString("admin_password"));
|
||||
Assert.assertEquals("o=TEST", json.getString("base_dn"));
|
||||
Assert.assertEquals("(&(objectclass=inetOrgPerson)(uid=USERNAME))", json.getString("filter"));
|
||||
Assert.assertEquals("devnull@teedy.io", json.getString("default_email"));
|
||||
Assert.assertEquals(100000000L, json.getJsonNumber("default_storage").longValue());
|
||||
|
||||
// Login with a LDAP user
|
||||
String ldapTopen = clientUtil.login("ldap1", "secret", false);
|
||||
|
||||
// Check user informations
|
||||
json = target().path("/user").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, ldapTopen)
|
||||
.get(JsonObject.class);
|
||||
Assert.assertEquals("ldap1@teedy.io", json.getString("email"));
|
||||
|
||||
// List all documents
|
||||
json = target().path("/document/list")
|
||||
.queryParam("sort_column", 3)
|
||||
.queryParam("asc", true)
|
||||
.request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, ldapTopen)
|
||||
.get(JsonObject.class);
|
||||
JsonArray documents = json.getJsonArray("documents");
|
||||
Assert.assertEquals(0, documents.size());
|
||||
|
||||
// Stop LDAP server
|
||||
ldapServer.stop();
|
||||
directoryService.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,12 @@ public class TestAuditLogResource extends BaseJerseyTest {
|
||||
Assert.assertEquals(countByClass(logs, "Document"), 1);
|
||||
Assert.assertEquals(countByClass(logs, "Acl"), 2);
|
||||
Assert.assertEquals(countByClass(logs, "File"), 1);
|
||||
|
||||
// Delete auditlog1
|
||||
String adminToken = adminToken();
|
||||
target().path("/user/auditlog1").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -273,6 +273,7 @@ public class TestDocumentResource extends BaseJerseyTest {
|
||||
Assert.assertFalse(relations.getJsonObject(0).getBoolean("source"));
|
||||
Assert.assertEquals("My super title document 2", relations.getJsonObject(0).getString("title"));
|
||||
Assert.assertFalse(json.containsKey("files"));
|
||||
Assert.assertEquals(file1Id, json.getString("file_id"));
|
||||
|
||||
// Get document 2
|
||||
json = target().path("/document/" + document2Id).request()
|
||||
@@ -285,6 +286,7 @@ public class TestDocumentResource extends BaseJerseyTest {
|
||||
Assert.assertTrue(relations.getJsonObject(0).getBoolean("source"));
|
||||
Assert.assertEquals("My super title document 1", relations.getJsonObject(0).getString("title"));
|
||||
Assert.assertFalse(json.containsKey("files"));
|
||||
Assert.assertEquals(file1Id, json.getString("file_id"));
|
||||
|
||||
// Create a tag
|
||||
json = target().path("/tag").request()
|
||||
@@ -818,7 +820,7 @@ public class TestDocumentResource extends BaseJerseyTest {
|
||||
@Test
|
||||
public void testCustomMetadata() {
|
||||
// Login admin
|
||||
String adminToken = clientUtil.login("admin", "admin", false);
|
||||
String adminToken = adminToken();
|
||||
|
||||
// Login metadata1
|
||||
clientUtil.createUser("metadata1");
|
||||
|
||||
@@ -105,7 +105,7 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
Assert.assertEquals("PIA00452.jpg", files.getJsonObject(0).getString("name"));
|
||||
Assert.assertEquals("image/jpeg", files.getJsonObject(0).getString("mimetype"));
|
||||
Assert.assertEquals(0, files.getJsonObject(0).getInt("version"));
|
||||
Assert.assertEquals(163510L, files.getJsonObject(0).getJsonNumber("size").longValue());
|
||||
Assert.assertEquals(FILE_PIA_00452_JPG_SIZE, files.getJsonObject(0).getJsonNumber("size").longValue());
|
||||
Assert.assertEquals(file2Id, files.getJsonObject(1).getString("id"));
|
||||
Assert.assertEquals("PIA00452.jpg", files.getJsonObject(1).getString("name"));
|
||||
Assert.assertEquals(0, files.getJsonObject(1).getInt("version"));
|
||||
@@ -370,7 +370,7 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
.get();
|
||||
is = (InputStream) response.getEntity();
|
||||
fileBytes = ByteStreams.toByteArray(is);
|
||||
Assert.assertEquals(163510, fileBytes.length);
|
||||
Assert.assertEquals(FILE_PIA_00452_JPG_SIZE, fileBytes.length);
|
||||
|
||||
// Create another document
|
||||
String document2Id = clientUtil.createDocument(fileOrphanToken);
|
||||
@@ -415,28 +415,19 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
String file1Id = clientUtil.addFileToDocument(FILE_EINSTEIN_ROOSEVELT_LETTER_PNG, fileQuotaToken, null);
|
||||
|
||||
// Check current quota
|
||||
JsonObject json = target().path("/user").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaToken)
|
||||
.get(JsonObject.class);
|
||||
Assert.assertEquals(292641L, json.getJsonNumber("storage_current").longValue());
|
||||
Assert.assertEquals(FILE_EINSTEIN_ROOSEVELT_LETTER_PNG_SIZE, getUserQuota(fileQuotaToken));
|
||||
|
||||
// Add a file (292641 bytes large)
|
||||
clientUtil.addFileToDocument(FILE_EINSTEIN_ROOSEVELT_LETTER_PNG, fileQuotaToken, null);
|
||||
|
||||
// Check current quota
|
||||
json = target().path("/user").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaToken)
|
||||
.get(JsonObject.class);
|
||||
Assert.assertEquals(585282L, json.getJsonNumber("storage_current").longValue());
|
||||
Assert.assertEquals(FILE_EINSTEIN_ROOSEVELT_LETTER_PNG_SIZE * 2, getUserQuota(fileQuotaToken));
|
||||
|
||||
// Add a file (292641 bytes large)
|
||||
clientUtil.addFileToDocument(FILE_EINSTEIN_ROOSEVELT_LETTER_PNG, fileQuotaToken, null);
|
||||
|
||||
// Check current quota
|
||||
json = target().path("/user").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaToken)
|
||||
.get(JsonObject.class);
|
||||
Assert.assertEquals(877923L, json.getJsonNumber("storage_current").longValue());
|
||||
Assert.assertEquals(FILE_EINSTEIN_ROOSEVELT_LETTER_PNG_SIZE * 3, getUserQuota(fileQuotaToken));
|
||||
|
||||
// Add a file (292641 bytes large)
|
||||
try {
|
||||
@@ -446,16 +437,13 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
}
|
||||
|
||||
// Deletes a file
|
||||
json = target().path("/file/" + file1Id).request()
|
||||
JsonObject json = target().path("/file/" + file1Id).request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaToken)
|
||||
.delete(JsonObject.class);
|
||||
Assert.assertEquals("ok", json.getString("status"));
|
||||
|
||||
// Check current quota
|
||||
json = target().path("/user").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaToken)
|
||||
.get(JsonObject.class);
|
||||
Assert.assertEquals(585282L, json.getJsonNumber("storage_current").longValue());
|
||||
Assert.assertEquals(FILE_EINSTEIN_ROOSEVELT_LETTER_PNG_SIZE * 2, getUserQuota(fileQuotaToken));
|
||||
|
||||
// Create a document
|
||||
long create1Date = new Date().getTime();
|
||||
@@ -472,10 +460,7 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
clientUtil.addFileToDocument(FILE_PIA_00452_JPG, fileQuotaToken, document1Id);
|
||||
|
||||
// Check current quota
|
||||
json = target().path("/user").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaToken)
|
||||
.get(JsonObject.class);
|
||||
Assert.assertEquals(748792, json.getJsonNumber("storage_current").longValue());
|
||||
Assert.assertEquals(FILE_EINSTEIN_ROOSEVELT_LETTER_PNG_SIZE * 2 + FILE_PIA_00452_JPG_SIZE, getUserQuota(fileQuotaToken));
|
||||
|
||||
// Deletes the document
|
||||
json = target().path("/document/" + document1Id).request()
|
||||
@@ -484,9 +469,12 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
Assert.assertEquals("ok", json.getString("status"));
|
||||
|
||||
// Check current quota
|
||||
json = target().path("/user").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, fileQuotaToken)
|
||||
.get(JsonObject.class);
|
||||
Assert.assertEquals(585282L, json.getJsonNumber("storage_current").longValue());
|
||||
Assert.assertEquals(FILE_EINSTEIN_ROOSEVELT_LETTER_PNG_SIZE * 2, getUserQuota(fileQuotaToken));
|
||||
}
|
||||
|
||||
private long getUserQuota(String userToken) {
|
||||
return target().path("/user").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, userToken)
|
||||
.get(JsonObject.class).getJsonNumber("storage_current").longValue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public class TestGroupResource extends BaseJerseyTest {
|
||||
@Test
|
||||
public void testGroupResource() {
|
||||
// Login admin
|
||||
String adminToken = clientUtil.login("admin", "admin", false);
|
||||
String adminToken = adminToken();
|
||||
|
||||
// Create group hierarchy
|
||||
clientUtil.createGroup("g1");
|
||||
@@ -188,5 +188,25 @@ public class TestGroupResource extends BaseJerseyTest {
|
||||
Assert.assertEquals(2, groups.size());
|
||||
Assert.assertTrue(groupList.contains("g11"));
|
||||
Assert.assertTrue(groupList.contains("g112"));
|
||||
|
||||
// Delete all remaining groups and users
|
||||
target().path("/group/g11").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.delete(JsonObject.class);
|
||||
target().path("/group/g12new").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.delete(JsonObject.class);
|
||||
target().path("/group/g111").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.delete(JsonObject.class);
|
||||
target().path("/group/g112").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.delete(JsonObject.class);
|
||||
target().path("/user/group1").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.delete();
|
||||
target().path("/user/admin2").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ public class TestMetadataResource extends BaseJerseyTest {
|
||||
@Test
|
||||
public void testMetadataResource() {
|
||||
// Login admin
|
||||
String adminToken = clientUtil.login("admin", "admin", false);
|
||||
String adminToken = adminToken();
|
||||
|
||||
// Get all metadata with admin
|
||||
JsonObject json = target().path("/metadata")
|
||||
@@ -79,4 +79,4 @@ public class TestMetadataResource extends BaseJerseyTest {
|
||||
metadata = json.getJsonArray("metadata");
|
||||
Assert.assertEquals(0, metadata.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ public class TestRouteModelResource extends BaseJerseyTest {
|
||||
@Test
|
||||
public void testRouteModelResource() {
|
||||
// Login admin
|
||||
String adminToken = clientUtil.login("admin", "admin", false);
|
||||
String adminToken = adminToken();
|
||||
|
||||
// Login routeModel1
|
||||
clientUtil.createUser("routeModel1");
|
||||
@@ -137,5 +137,10 @@ public class TestRouteModelResource extends BaseJerseyTest {
|
||||
.get(JsonObject.class);
|
||||
routeModels = json.getJsonArray("routemodels");
|
||||
Assert.assertEquals(1, routeModels.size());
|
||||
|
||||
// Deletes routeModel1 user
|
||||
target().path("/user/routeModel1").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ public class TestRouteResource extends BaseJerseyTest {
|
||||
String route1Token = clientUtil.login("route1");
|
||||
|
||||
// Login admin
|
||||
String adminToken = clientUtil.login("admin", "admin", false);
|
||||
String adminToken = adminToken();
|
||||
|
||||
// Change SMTP configuration to target Wiser
|
||||
target().path("/app/config_smtp").request()
|
||||
@@ -364,7 +364,7 @@ public class TestRouteResource extends BaseJerseyTest {
|
||||
@Test
|
||||
public void testTagActions() {
|
||||
// Login admin
|
||||
String adminToken = clientUtil.login("admin", "admin", false);
|
||||
String adminToken = adminToken();
|
||||
|
||||
// Create an Approved tag
|
||||
JsonObject json = target().path("/tag").request()
|
||||
@@ -511,4 +511,4 @@ public class TestRouteResource extends BaseJerseyTest {
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.delete(JsonObject.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,12 @@ public class TestSecurity extends BaseJerseyTest {
|
||||
|
||||
// User testsecurity logs out
|
||||
clientUtil.logout(testSecurityToken);
|
||||
|
||||
// Delete the user
|
||||
String adminToken = adminToken();
|
||||
target().path("/user/testsecurity").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -98,5 +104,11 @@ public class TestSecurity extends BaseJerseyTest {
|
||||
.header(HeaderBasedSecurityFilter.AUTHENTICATED_USER_HEADER, "idontexist")
|
||||
.get()
|
||||
.getStatus());
|
||||
|
||||
// Delete the user
|
||||
String adminToken = adminToken();
|
||||
target().path("/user/header_auth_test").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.delete();
|
||||
}
|
||||
}
|
||||
@@ -127,5 +127,14 @@ public class TestShareResource extends BaseJerseyTest {
|
||||
Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.readEntity(JsonObject.class);
|
||||
Assert.assertEquals("ShareNotFound", json.getString("type"));
|
||||
|
||||
// Deletes share1 and share 2
|
||||
String adminToken = adminToken();
|
||||
target().path("/user/share1").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.delete();
|
||||
target().path("/user/share2").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.delete();
|
||||
}
|
||||
}
|
||||
@@ -230,5 +230,11 @@ public class TestTagResource extends BaseJerseyTest {
|
||||
Assert.assertEquals(1, tags.size());
|
||||
Assert.assertEquals("UpdatedName", tags.getJsonObject(0).getString("name"));
|
||||
Assert.assertNull(tags.getJsonObject(0).get("parent"));
|
||||
|
||||
// Deletes user tag1
|
||||
String adminToken = adminToken();
|
||||
target().path("/user/tag1").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.delete();
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ public class TestThemeResource extends BaseJerseyTest {
|
||||
@Test
|
||||
public void testThemeResource() throws Exception {
|
||||
// Login admin
|
||||
String adminToken = clientUtil.login("admin", "admin", false);
|
||||
String adminToken = adminToken();
|
||||
|
||||
// Get the stylesheet anonymously
|
||||
String stylesheet = target().path("/theme/stylesheet").request()
|
||||
@@ -103,5 +103,13 @@ public class TestThemeResource extends BaseJerseyTest {
|
||||
// Get the background
|
||||
response = target().path("/theme/image/background").request().get();
|
||||
Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
|
||||
|
||||
// Reset the main color as admin
|
||||
target().path("/theme").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.post(Entity.form(new Form()
|
||||
.param("color", "#ffffff")
|
||||
.param("name", "Teedy")
|
||||
.param("css", "")), JsonObject.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public class TestUserResource extends BaseJerseyTest {
|
||||
clientUtil.createUser("alice");
|
||||
|
||||
// Login admin
|
||||
String adminToken = clientUtil.login("admin", "admin", false);
|
||||
String adminToken = adminToken();
|
||||
|
||||
// List all users
|
||||
json = target().path("/user/list")
|
||||
@@ -75,7 +75,7 @@ public class TestUserResource extends BaseJerseyTest {
|
||||
response = target().path("/user").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.put(Entity.form(new Form()
|
||||
.param("username", "bob-")
|
||||
.param("username", "bob/")
|
||||
.param("email", "bob@docs.com")
|
||||
.param("password", "12345678")
|
||||
.param("storage_quota", "10")));
|
||||
@@ -128,15 +128,6 @@ public class TestUserResource extends BaseJerseyTest {
|
||||
json = response.readEntity(JsonObject.class);
|
||||
Assert.assertEquals("AlreadyExistingUsername", json.getString("type"));
|
||||
|
||||
// Check if a username is free : OK
|
||||
target().path("/user/check_username").queryParam("username", "carol").request().get(JsonObject.class);
|
||||
|
||||
// Check if a username is free : KO
|
||||
response = target().path("/user/check_username").queryParam("username", "alice").request().get();
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.readEntity(JsonObject.class);
|
||||
Assert.assertEquals("ko", json.getString("status"));
|
||||
|
||||
// Login alice with extra whitespaces
|
||||
response = target().path("/user/login").request()
|
||||
.post(Entity.form(new Form()
|
||||
@@ -239,6 +230,11 @@ public class TestUserResource extends BaseJerseyTest {
|
||||
.param("username", "alice")
|
||||
.param("password", "12345678")));
|
||||
Assert.assertEquals(Status.FORBIDDEN, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Delete user bob
|
||||
target().path("/user").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, bobToken)
|
||||
.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,7 +246,7 @@ public class TestUserResource extends BaseJerseyTest {
|
||||
clientUtil.createUser("admin_user1");
|
||||
|
||||
// Login admin
|
||||
String adminToken = clientUtil.login("admin", "admin", false);
|
||||
String adminToken = adminToken();
|
||||
|
||||
// Check admin information
|
||||
JsonObject json = target().path("/user").request()
|
||||
@@ -336,7 +332,7 @@ public class TestUserResource extends BaseJerseyTest {
|
||||
@Test
|
||||
public void testTotp() {
|
||||
// Login admin
|
||||
String adminToken = clientUtil.login("admin", "admin", false);
|
||||
String adminToken = adminToken();
|
||||
|
||||
// Create totp1 user
|
||||
clientUtil.createUser("totp1");
|
||||
@@ -420,12 +416,18 @@ public class TestUserResource extends BaseJerseyTest {
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, totp1Token)
|
||||
.get(JsonObject.class);
|
||||
Assert.assertFalse(json.getBoolean("totp_enabled"));
|
||||
|
||||
// Delete totp1
|
||||
response = target().path("/user/totp1").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.delete();
|
||||
Assert.assertEquals(Response.Status.OK, Response.Status.fromStatusCode(response.getStatus()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResetPassword() throws Exception {
|
||||
// Login admin
|
||||
String adminToken = clientUtil.login("admin", "admin", false);
|
||||
String adminToken = adminToken();
|
||||
|
||||
// Change SMTP configuration to target Wiser
|
||||
target().path("/app/config_smtp").request()
|
||||
@@ -492,5 +494,11 @@ public class TestUserResource extends BaseJerseyTest {
|
||||
Assert.assertEquals(Response.Status.BAD_REQUEST, Response.Status.fromStatusCode(response.getStatus()));
|
||||
json = response.readEntity(JsonObject.class);
|
||||
Assert.assertEquals("KeyNotFound", json.getString("type"));
|
||||
|
||||
// Delete absent_minded
|
||||
response = target().path("/user/absent_minded").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.delete();
|
||||
Assert.assertEquals(Response.Status.OK, Response.Status.fromStatusCode(response.getStatus()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class TestVocabularyResource extends BaseJerseyTest {
|
||||
String vocabulary1Token = clientUtil.login("vocabulary1");
|
||||
|
||||
// Login admin
|
||||
String adminToken = clientUtil.login("admin", "admin", false);
|
||||
String adminToken = adminToken();
|
||||
|
||||
// Get coverage vocabularies entries
|
||||
JsonObject json = target().path("/vocabulary/coverage").request()
|
||||
@@ -109,4 +109,4 @@ public class TestVocabularyResource extends BaseJerseyTest {
|
||||
.get(JsonObject.class);
|
||||
Assert.assertEquals(0, json.getJsonArray("entries").size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public class TestWebhookResource extends BaseJerseyTest {
|
||||
@Test
|
||||
public void testWebhookResource() {
|
||||
// Login admin
|
||||
String adminToken = clientUtil.login("admin", "admin", false);
|
||||
String adminToken = adminToken();
|
||||
|
||||
// Login webhook1
|
||||
clientUtil.createUser("webhook1");
|
||||
@@ -84,5 +84,10 @@ public class TestWebhookResource extends BaseJerseyTest {
|
||||
.get(JsonObject.class);
|
||||
webhooks = json.getJsonArray("webhooks");
|
||||
Assert.assertEquals(0, webhooks.size());
|
||||
|
||||
// Deletes webhook1
|
||||
target().path("/user/webhook1").request()
|
||||
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
|
||||
.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,492 @@
|
||||
package com.sismics.docs.rest.util;
|
||||
|
||||
import com.sismics.docs.core.dao.TagDao;
|
||||
import com.sismics.docs.core.dao.criteria.DocumentCriteria;
|
||||
import com.sismics.docs.core.dao.criteria.TagCriteria;
|
||||
import com.sismics.docs.core.dao.dto.TagDto;
|
||||
import com.sismics.docs.core.model.jpa.Tag;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import com.sismics.docs.rest.BaseTransactionalTest;
|
||||
import com.sismics.util.mime.MimeType;
|
||||
import org.apache.poi.ss.formula.functions.T;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class TestDocumentSearchCriteriaUtil extends BaseTransactionalTest {
|
||||
|
||||
@Test
|
||||
public void testHttpParamsBy() throws Exception {
|
||||
User user = createUser("user1");
|
||||
|
||||
DocumentCriteria documentCriteria = new DocumentCriteria();
|
||||
DocumentSearchCriteriaUtil.addHttpSearchParams(
|
||||
documentCriteria,
|
||||
"user1",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
Assert.assertEquals(documentCriteria.getCreatorId(), user.getId());
|
||||
|
||||
documentCriteria = new DocumentCriteria();
|
||||
DocumentSearchCriteriaUtil.addHttpSearchParams(
|
||||
documentCriteria,
|
||||
"missing",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
Assert.assertNotNull(documentCriteria.getCreatorId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpParamsCreatedAfter() {
|
||||
DocumentCriteria documentCriteria = new DocumentCriteria();
|
||||
DocumentSearchCriteriaUtil.addHttpSearchParams(
|
||||
documentCriteria,
|
||||
null,
|
||||
"2022-03-27",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
Assert.assertEquals(documentCriteria.getCreateDateMin(), new DateTime(2022, 3, 27, 0, 0, 0).toDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpParamsCreatedBefore() {
|
||||
DocumentCriteria documentCriteria = new DocumentCriteria();
|
||||
DocumentSearchCriteriaUtil.addHttpSearchParams(
|
||||
documentCriteria,
|
||||
null,
|
||||
null,
|
||||
"2022-03-27",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
Assert.assertEquals(documentCriteria.getCreateDateMax(), new DateTime(2022, 3, 27, 0, 0, 0).toDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpParamsFull() {
|
||||
DocumentCriteria documentCriteria = new DocumentCriteria();
|
||||
DocumentSearchCriteriaUtil.addHttpSearchParams(
|
||||
documentCriteria,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"full",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
Assert.assertEquals(documentCriteria.getFullSearch(), "full");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpParamsLang() {
|
||||
DocumentCriteria documentCriteria = new DocumentCriteria();
|
||||
DocumentSearchCriteriaUtil.addHttpSearchParams(
|
||||
documentCriteria,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"fra",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
Assert.assertEquals(documentCriteria.getLanguage(), "fra");
|
||||
|
||||
documentCriteria = new DocumentCriteria();
|
||||
DocumentSearchCriteriaUtil.addHttpSearchParams(
|
||||
documentCriteria,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"unknown",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
Assert.assertNotNull(documentCriteria.getLanguage());
|
||||
Assert.assertNotEquals(documentCriteria.getLanguage(), "unknown");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpParamsMime() {
|
||||
DocumentCriteria documentCriteria = new DocumentCriteria();
|
||||
DocumentSearchCriteriaUtil.addHttpSearchParams(
|
||||
documentCriteria,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
MimeType.IMAGE_GIF,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
Assert.assertEquals(documentCriteria.getMimeType(), MimeType.IMAGE_GIF);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpParamsShared() {
|
||||
DocumentCriteria documentCriteria = new DocumentCriteria();
|
||||
DocumentSearchCriteriaUtil.addHttpSearchParams(
|
||||
documentCriteria,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
Assert.assertTrue(documentCriteria.getShared());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpParamsSimple() {
|
||||
DocumentCriteria documentCriteria = new DocumentCriteria();
|
||||
DocumentSearchCriteriaUtil.addHttpSearchParams(
|
||||
documentCriteria,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"simple",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
Assert.assertEquals(documentCriteria.getSimpleSearch(), "simple");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpParamsTag() throws Exception {
|
||||
TagDao tagDao = new TagDao();
|
||||
|
||||
User user = createUser("user1");
|
||||
Tag tag1 = new Tag();
|
||||
tag1.setName("tag1");
|
||||
tag1.setColor("#bbb");
|
||||
tag1.setUserId(user.getId());
|
||||
tagDao.create(tag1, user.getId());
|
||||
|
||||
Tag tag2 = new Tag();
|
||||
tag2.setName("tag2");
|
||||
tag2.setColor("#bbb");
|
||||
tag2.setUserId(user.getId());
|
||||
tagDao.create(tag2, user.getId());
|
||||
|
||||
Tag tag3 = new Tag();
|
||||
tag3.setName("tag3");
|
||||
tag3.setColor("#bbb");
|
||||
tag3.setUserId(user.getId());
|
||||
tag3.setParentId(tag2.getId());
|
||||
tagDao.create(tag3, user.getId());
|
||||
|
||||
DocumentCriteria documentCriteria = new DocumentCriteria();
|
||||
List<TagDto> allTagDtoList = tagDao.findByCriteria(new TagCriteria(), null);
|
||||
DocumentSearchCriteriaUtil.addHttpSearchParams(
|
||||
documentCriteria,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"tag1",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
allTagDtoList
|
||||
);
|
||||
Assert.assertEquals(documentCriteria.getTagIdList(), List.of(Collections.singletonList(tag1.getId())));
|
||||
|
||||
documentCriteria = new DocumentCriteria();
|
||||
DocumentSearchCriteriaUtil.addHttpSearchParams(
|
||||
documentCriteria,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"tag2",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
allTagDtoList
|
||||
);
|
||||
Assert.assertEquals(documentCriteria.getTagIdList(), List.of(List.of(tag2.getId(), tag3.getId())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpParamsNotTag() throws Exception {
|
||||
TagDao tagDao = new TagDao();
|
||||
|
||||
User user = createUser("user1");
|
||||
Tag tag1 = new Tag();
|
||||
tag1.setName("tag1");
|
||||
tag1.setColor("#bbb");
|
||||
tag1.setUserId(user.getId());
|
||||
tagDao.create(tag1, user.getId());
|
||||
|
||||
Tag tag2 = new Tag();
|
||||
tag2.setName("tag2");
|
||||
tag2.setColor("#bbb");
|
||||
tag2.setUserId(user.getId());
|
||||
tagDao.create(tag2, user.getId());
|
||||
|
||||
Tag tag3 = new Tag();
|
||||
tag3.setName("tag3");
|
||||
tag3.setColor("#bbb");
|
||||
tag3.setUserId(user.getId());
|
||||
tag3.setParentId(tag2.getId());
|
||||
tagDao.create(tag3, user.getId());
|
||||
|
||||
DocumentCriteria documentCriteria = new DocumentCriteria();
|
||||
List<TagDto> allTagDtoList = tagDao.findByCriteria(new TagCriteria(), null);
|
||||
DocumentSearchCriteriaUtil.addHttpSearchParams(
|
||||
documentCriteria,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"tag1",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
allTagDtoList
|
||||
);
|
||||
Assert.assertEquals(documentCriteria.getExcludedTagIdList(), List.of(Collections.singletonList(tag1.getId())));
|
||||
|
||||
documentCriteria = new DocumentCriteria();
|
||||
DocumentSearchCriteriaUtil.addHttpSearchParams(
|
||||
documentCriteria,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"tag2",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
allTagDtoList
|
||||
);
|
||||
Assert.assertEquals(documentCriteria.getExcludedTagIdList(), List.of(List.of(tag2.getId(), tag3.getId())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpParamsTitle() {
|
||||
DocumentCriteria documentCriteria = new DocumentCriteria();
|
||||
DocumentSearchCriteriaUtil.addHttpSearchParams(
|
||||
documentCriteria,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"title1,title2",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
Assert.assertEquals(documentCriteria.getTitleList(), Arrays.asList(new String[]{"title1", "title2"}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpParamsUpdatedAfter() {
|
||||
DocumentCriteria documentCriteria = new DocumentCriteria();
|
||||
DocumentSearchCriteriaUtil.addHttpSearchParams(
|
||||
documentCriteria,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"2022-03-27",
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
Assert.assertEquals(documentCriteria.getUpdateDateMin(), new DateTime(2022, 3, 27, 0, 0, 0).toDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpParamsUpdatedBefore() {
|
||||
DocumentCriteria documentCriteria = new DocumentCriteria();
|
||||
DocumentSearchCriteriaUtil.addHttpSearchParams(
|
||||
documentCriteria,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"2022-03-27",
|
||||
null,
|
||||
null
|
||||
);
|
||||
Assert.assertEquals(documentCriteria.getUpdateDateMax(), new DateTime(2022, 3, 27, 0, 0, 0).toDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpParamsWorkflow() {
|
||||
DocumentCriteria documentCriteria = new DocumentCriteria();
|
||||
DocumentSearchCriteriaUtil.addHttpSearchParams(
|
||||
documentCriteria,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"me",
|
||||
null
|
||||
);
|
||||
Assert.assertTrue(documentCriteria.getActiveRoute());
|
||||
}
|
||||
|
||||
}
|
||||
15
docs-web/src/test/resources/log4j.properties
Normal file
15
docs-web/src/test/resources/log4j.properties
Normal file
@@ -0,0 +1,15 @@
|
||||
log4j.rootCategory=INFO, CONSOLE, MEMORY
|
||||
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.CONSOLE.layout.ConversionPattern=%d{DATE} %p %l %m %n
|
||||
log4j.appender.MEMORY=com.sismics.util.log4j.MemoryAppender
|
||||
log4j.appender.MEMORY.size=1000
|
||||
|
||||
log4j.logger.com.sismics=INFO
|
||||
log4j.logger.com.sismics.util.jpa=ERROR
|
||||
log4j.logger.org.hibernate=ERROR
|
||||
log4j.logger.org.apache.pdfbox=INFO
|
||||
log4j.logger.com.mchange=ERROR
|
||||
log4j.logger.org.apache.directory=ERROR
|
||||
log4j.logger.org.glassfish.grizzly=ERROR
|
||||
log4j.logger.org.odftoolkit=ERROR
|
||||
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%date [%t] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<appender name="MEMORY" class="com.sismics.util.logback.MemoryAppender">
|
||||
<size>1000</size>
|
||||
</appender>
|
||||
<logger name="org.apache.directory" level="ERROR"/>
|
||||
<logger name="com.sismics" level="INFO"/>
|
||||
<logger name="org.glassfish.grizzly" level="ERROR"/>
|
||||
<logger name="org.apache.pdfbox" level="INFO"/>
|
||||
<logger name="org.hibernate" level="ERROR"/>
|
||||
<logger name="com.mchange" level="ERROR"/>
|
||||
<logger name="com.sismics.util.jpa" level="ERROR"/>
|
||||
<logger name="org.odftoolkit" level="ERROR"/>
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="MEMORY"/>
|
||||
</root>
|
||||
</configuration>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user