Compare commits

...

196 Commits

Author SHA1 Message Date
bgamard 90d42634bb Merge branch 'master' into sismics_prod
# Conflicts:
#	Dockerfile
2023-10-17 20:42:30 +02:00
Julien Kirch a89543b555
Make search for documents faster for large dataset (#698) 2023-10-08 22:07:01 +02:00
Benjamin Gamard ce30b1a6ff
fix build 2023-09-15 22:05:04 +02:00
Orland Karamani 1b382004cb
Albanian Language Support (#719)
Co-authored-by: Orlando Karamani <orlandothemover@gmail.com>
2023-09-14 16:51:11 +02:00
Julien Kirch ab7ff25929
Store file size in DB (#704) 2023-09-14 16:50:39 +02:00
Julien Kirch eedf19ad9d
Fix no favicon on shares #580 (#718) 2023-09-08 15:43:35 +02:00
Julien Kirch 941ace99c6
Fix typo in /file/:id/versions description (#717) 2023-09-07 16:46:43 +02:00
bgamard 95e0b870f6 Merge remote-tracking branch 'origin/master' 2023-06-29 21:33:12 +02:00
bgamard 2bdb2dc34f #678: reopen ldap connection for each login 2023-06-29 21:33:05 +02:00
Julien Kirch 22a44d0c8d
Finding several documents by their title in a single query (#696) 2023-06-06 21:31:01 +02:00
Julien Kirch a9cdbdc03e
Add missing french translations (#694) 2023-06-05 16:02:55 +02:00
Julien Kirch 3fd5470eae
Add mention in the API doc that document endpoint returns the document's metadata (#695) 2023-06-04 21:49:36 +02:00
Mario Voigt 39f96cbd28
Update config.properties (#693)
fix db version to reflect the most recent
2023-06-04 21:48:55 +02:00
Mario Voigt 4501f10429
fix comma to make valid language de.json file again 2023-05-07 11:56:34 +02:00
Mario Voigt bd0cde7e87
Add support for STARTTLS for Inbox Scanning (#682) 2023-04-25 18:27:46 +02:00
bgamard dd36e08d7d #680: warning when using H2 database 2023-04-22 00:47:01 +02:00
Jose Luis Montes Jiménez 4634def93e
updating README.md (#681)
H2 database should only be use for testing, setting the docker-compose with postgreSQL as default way.
2023-04-22 00:12:48 +02:00
bgamard 1974a8bb8d #668: cleanup hibernate dependencies 2023-04-12 17:58:51 +02:00
bgamard e9a6609593 #668: jetty 11 deployment 2023-04-12 13:35:54 +02:00
bgamard b20577026e Closes #668: upgrade jetty/servlet-api/jersey 2023-04-09 21:31:53 +02:00
bgamard dae9e137f7 Merge remote-tracking branch 'origin/master' 2023-03-22 10:23:18 +01:00
bgamard 1509d0c5bb revert h2 upgrade 2023-03-22 10:23:11 +01:00
Mario Voigt 430ebbd1c5
support ldaps (#670) 2023-03-21 21:56:14 +01:00
bgamard b561eaee6d portuguese translation 2023-03-20 20:20:52 +01:00
bgamard 1aa21c3762 bump dependencies 2023-03-19 14:28:22 +01:00
bgamard c8a67177d8 next dev iteration 2023-03-12 14:11:52 +01:00
bgamard 59597e962d 1.11 2023-03-12 13:58:03 +01:00
bgamard c85a951a9e upgrade base image 2023-03-12 13:52:30 +01:00
bgamard 7f47a17633 upgrade jetty 2023-03-12 13:45:36 +01:00
bgamard 690c961a55 Merge remote-tracking branch 'origin/master' 2023-03-12 13:35:51 +01:00
bgamard 21efd1e4a7 Closes #658 2023-03-12 13:35:35 +01:00
@RandyMcMillan ad27228429
docker-compose.yml: add example config (#665) 2023-02-20 11:51:39 +01:00
@RandyMcMillan dd4a1667ca
.gitignore: add docs/.gitkeep (#664) 2023-02-20 11:51:30 +01:00
@RandyMcMillan 399d2b7951
minor grammar corrections (#663) 2023-02-19 21:31:30 +01:00
bgamard d51dfd6636 #647: fix doc 2022-08-26 18:18:06 +02:00
bgamard ca85c1fa9f #647: always return OK on password lost route 2022-08-26 18:15:49 +02:00
bgamard 5e7f06070e keep filename in temporary file 2022-05-16 19:22:54 +02:00
bgamard dc0c20cd0c moved tests 2022-05-16 18:53:08 +02:00
bgamard 98aa33341a moved tests 2022-05-16 18:50:19 +02:00
bgamard 1f7c0afc1e Closes #639: rework mime type resolution using java api 2022-05-16 18:44:26 +02:00
bgamard 1ccce3f942 rename 2022-05-05 18:15:24 +02:00
Uli 90d5bc8de7
Allow the . (dot) and @ (at) character in usernames (#637)
Co-authored-by: Uli Koeth <uli@kiot.eu>
2022-05-05 17:48:45 +02:00
bgamard c6a685d7c0 Closes #620: delete a non-existing document should return 404 2022-04-17 13:35:29 +02:00
bgamard e6cfd899e5 Closes #632: validate POST /app/config_inbox and update documentation 2022-04-17 13:23:22 +02:00
Julien Kirch bd23f14792
Add doc for search syntax (#634) 2022-04-17 13:10:01 +02:00
Julien Kirch 46f6b9e537
Download zip of files not in same document (#591) 2022-04-15 10:18:39 +02:00
Julien Kirch d5832c48e1
Small code cleaning 2022-03-21 11:36:25 +01:00
Julien Kirch 64ec0f63ca
Add parameter to return the files when searching for a document (#582) 2022-03-20 11:36:28 +01:00
Ben Grabham 0b7c42e814
Check if environment variables are not empty strings as well as not null (#623) 2022-02-20 15:48:37 +01:00
bgamard d8dc63fc98 Merge remote-tracking branch 'origin/master' 2022-02-02 21:18:06 +01:00
bgamard 81a7f154c2 logs only for admin 2022-02-02 21:17:58 +01:00
StaryVena af3263d471
Add OCR support for Czech language (#613)
Co-authored-by: Vaclav Uher <vaclav.uher@bruker.com>
2022-01-26 15:27:14 +01:00
Dan Schaper bbe5f19997
Tag latest on master, tag version on github tag. (#612)
Signed-off-by: Dan Schaper <dan.schaper@pi-hole.net>
2022-01-25 10:37:47 +01:00
Benjamin Gamard f33650c099
fix action 2022-01-21 13:51:16 +01:00
Benjamin Gamard 58f81ec851
fix action 2022-01-21 13:37:31 +01:00
Dan Schaper c9262eb204
Add build tags and labels (#608)
Fixes Docker images always build as 'latest' #607

Signed-off-by: Dan Schaper <dan.schaper@pi-hole.net>
2022-01-21 13:35:39 +01:00
bgamard 3637b832e5 test the new mime type detection 2022-01-17 14:37:22 +01:00
Joost Timmerman ee56cfe2b4
Support audio mime (#574) 2022-01-17 14:24:50 +01:00
bgamard 721410c7d0 add test dependencies 2022-01-13 00:15:37 +01:00
bgamard f0310e3933 add test dependencies 2022-01-13 00:06:29 +01:00
bgamard 302d7cccc4 run tests + fix docker username 2022-01-12 23:59:43 +01:00
Dan Schaper f9977d5ce6
Actions workflow (#601)
Signed-off-by: Dan Schaper <dan@glacialmagma.com>
2022-01-12 23:49:34 +01:00
bgamard 0a927fd320 add application/x-www-form-urlencoded to delete requests 2022-01-02 16:46:20 +01:00
bgamard 523501a592 consumes application/x-www-form-urlencoded 2022-01-02 16:40:01 +01:00
bgamard ff8155be6a upgrade docker image to use jetty 9.4.36 2022-01-02 16:06:36 +01:00
bgamard 6c5d697051 Merge remote-tracking branch 'origin/master' 2022-01-02 15:39:11 +01:00
bgamard b19145160e release 1.10 2022-01-02 15:39:00 +01:00
Roland Illig c7ada71ef5
proofread German translation (#566)
* plural forms
* spelling of composed words
* spaces between numbers and measurement units
* typographic ellipsis (\u2026) instead of three dots
2021-11-20 20:34:36 +01:00
bgamard 4951229576 escape ngTranslate parameters 2021-11-16 20:01:36 +01:00
Julien Kirch d98c1bddec
Add custom parameter for exact search by title 2021-10-12 13:50:32 +02:00
Dan Schaper b0d0e93364
Remove duplicate tesseact language and alphabetize (#579)
Signed-off-by: Dan Schaper <dan@glacialmagma.com>
2021-09-30 13:23:58 +02:00
Benjamin Gamard f20a562439
remove form url encoded from baseresource 2021-08-20 10:45:08 +02:00
Hung Nguyen 4ae8475f5e
Add Vietnamese language support (#549) 2021-06-21 10:51:31 +02:00
Benjamin Gamard fd4c627c61
remove travis 2021-05-12 19:38:58 +02:00
Benjamin Gamard a867d48232
remove travis 2021-05-12 19:38:45 +02:00
Somebodyisnobody f6bf61fce9
Update de.json (#532)
Fix typo
2021-03-31 19:08:58 +02:00
bgamard c60c9a8f74 Merge remote-tracking branch 'origin/master' 2021-02-12 21:54:33 +01:00
bgamard dc021ab71e Closes #520: downgrade H2 to 1.4.199 2021-02-12 21:54:25 +01:00
Pascal Pischel 18b5551f6c
Fix german translation 2021-02-12 21:48:57 +01:00
bgamard 6fcd8771a5 upgrade to java 11 + upgrade libraries 2021-01-25 22:40:58 +01:00
bgamard 1fef4c3d2e next dev iteration + cleanup stress project 2021-01-25 21:31:14 +01:00
bgamard ee6ed2bf0b v1.9 2021-01-25 21:27:22 +01:00
bgamard 57b67fee09 Closes #458: fix css glitch on mobile 2021-01-21 18:14:39 +01:00
bgamard a6cbacae72 Closes #509: guest users cannot share and unshare 2021-01-21 17:58:36 +01:00
Vec7or 1e0f8e2484
Closes #472: redirect to previous URL after login 2021-01-21 17:44:48 +01:00
bgamard bcb4c6d7b0 Merge remote-tracking branch 'origin/master' 2021-01-21 17:39:23 +01:00
bgamard ea1d5907c1 #497: fix npe in unauthenticated cases 2021-01-21 17:39:01 +01:00
Vegard Hoff Walmsness 05bac38fc3
Norwegian language support 2021-01-14 20:20:16 +01:00
Cornelicorn 69746cd369
#486: Fix importer default file filter 2021-01-06 13:51:29 +01:00
Vec7or ff3db531e5
Configure bcrypt work 2021-01-05 18:59:18 +01:00
bgamard 558de7ba3f README.md 2020-12-31 07:50:04 +01:00
Vec7or af15116bf9
Upgrade bcrypt library + explain env variables 2020-12-31 07:46:00 +01:00
Benjamin Gamard 36e5a9747b
Merge pull request #489 from Vec7or/fix-color-bug
Fix tag-colors inside inherited acl
2020-12-30 23:45:57 +01:00
Vec7or 1d66b47f5f Fix tag-colors inside inherited acl 2020-12-30 17:33:24 +01:00
Evil McJerkface 1346dd3616
Add option to specify a particular IMAP folder (aka "label" in Gmail) (#477) 2020-11-22 13:39:39 +01:00
Evil McJerkface b6ec5e108b
Added support for TLS & STARTTLS for SMTP connections. If port 465 is configured, TLS will be assumed. If port 587 is used, STARTTLS is assumed. (#478)
Closes #353: Added support for TLS & STARTTLS for SMTP connections
2020-11-19 10:15:40 +01:00
bgamard 5b2833350c Closes #391: change english date format to yyyy/mm/dd 2020-10-31 20:20:11 +01:00
bgamard 66acb380ab Closes #451: convert lob content to text for pgsql 2020-10-31 20:14:06 +01:00
bgamard 00c62f2ad4 Closes #467: italian translation 2020-10-31 20:11:55 +01:00
bgamard 7205863d95 Closes #469: make sure the IP sent by the forward proxy is not bigger than 45 chars 2020-10-23 19:31:27 +02:00
Pyrox 2a4274d583
Italian translation (#465) 2020-10-16 13:54:49 +02:00
bgamard 087184b598 Closes #466: remove duplicate translation resources 2020-10-16 13:53:12 +02:00
bgamard e5600e0be7 add fallback for el language 2020-10-14 22:54:22 +02:00
Benjamin Gamard 964f3128d2
Merge pull request #463 from kazelot/dev-language-pl
Add  polish language
2020-10-14 22:49:57 +02:00
marcin 69905cdc55 Merge with last changes in master branch
Fix polish translation
2020-10-14 22:42:59 +02:00
marcin bf4e277db7 Add translation to timeago.js
Update translation
2020-10-14 19:07:40 +02:00
bgamard eaa7cca278 Closes #460: minification error following #455 2020-10-14 18:45:18 +02:00
marcin 0e115bb808 Translate to polish 2020-10-14 11:57:00 +02:00
marcin 1897f5567b Initial commit 2020-10-14 10:49:08 +02:00
bgamard d647528b3c #455: greek translation of angular-timeago by @gdepountis 2020-10-12 19:36:59 +02:00
bgamard 07d42cdb9c Merge remote-tracking branch 'origin/master' 2020-10-12 10:44:41 +02:00
bgamard dabb960c94 #455: greek translation by @gdepountis 2020-10-12 10:44:28 +02:00
Benjamin Gamard c71e794051
Merge pull request #454 from vmario89/patch-3
Update de.json
2020-10-08 12:52:43 +02:00
Mario Voigt 1584c0cbb2
Update de.json
Small lang fix in de.json
2020-10-08 09:52:38 +02:00
bgamard 22f0f1abf4 Closes #453: load gravatar icons in HTTPS 2020-10-06 16:37:52 +02:00
Jean-Marc Tremeaux 205f92d093 Upgrade to JDK 11.0.8 2020-09-24 13:12:45 +02:00
Jean-Marc Tremeaux 7488ac15a7 Merge remote-tracking branch 'origin/master' 2020-09-24 12:45:08 +02:00
bgamard 44f5db993a #451: remove @Lob on file content 2020-09-13 17:58:28 +02:00
bgamard f76eae23ca Merge remote-tracking branch 'origin/master' 2020-09-10 20:44:20 +02:00
bgamard 5e2a18f819 #451: update the file content with an Hibernate query instead of a native query 2020-09-10 20:42:51 +02:00
Benjamin Gamard 2f6e5d53c2
Merge pull request #449 from muhsinkutay/patch-1
Fix typo in English translation
2020-09-08 02:02:59 +02:00
muhsinkutay 50e6c4d965
Update en.json
Line 6: Misspelling
2020-09-08 02:51:53 +03:00
bgamard 3ad0554a7d use org.apache.directory.api for LDAP instead of apacheds 2020-08-29 19:10:14 +02:00
bgamard 113ec78c67 import less apacheds dependencies 2020-08-28 19:33:58 +02:00
bgamard f814927eca update README.md 2020-08-28 18:10:28 +02:00
bgamard a9719feeec LDAP support, courtesy of an anonymous donator 2020-08-28 18:09:54 +02:00
bgamard 6dc4f1b448 #423: fulltext search by default 2020-08-28 17:47:07 +02:00
bgamard e1fa17691d Merge remote-tracking branch 'origin/master' 2020-08-28 17:34:03 +02:00
bgamard 42e61d6e1f #423: fulltext search by default 2020-08-28 17:33:27 +02:00
Jamie Magee 2bf3e6bd3c
Danish language support (#438) 2020-08-02 23:32:29 +02:00
Benjamin Gamard 608b2f868d
Doc about prebuilt Docker image for bulk importer 2020-06-24 21:54:09 +02:00
Benjamin Gamard 46638bab5b
Build and push docs-importer to Docker Hub 2020-06-24 21:18:37 +02:00
Carl Reid 4607362e46
Add file filter to importer (#426) 2020-06-23 22:31:49 +02:00
Cornelicorn 041b2dfcc1
Fix tag-adding bugs of the importer (#427) 2020-06-21 14:48:13 +02:00
Carl Reid 7ad0dd43e2
Remove COPY node_modules from Dockerfile (#425) 2020-06-21 13:45:23 +02:00
buherman11 35339f7328
Fixed sending workflow emails to previous assignee (#422) 2020-06-10 12:04:09 +02:00
Gabisonfire e474e7cd75
Added option to copy a file before it is deleted after being imported (#418) 2020-06-07 20:13:04 +02:00
Cornelicorn 612fab2aef
Improve the file importer (#415)
Improve the bulk importer (tags by filename, document language, Docker container)
2020-05-22 15:18:19 +02:00
bgamard 3f67bd471b increase default heap space to 1GB 2020-05-19 15:33:23 +02:00
bgamard cb29dcd6cc handle all content extraction errors 2020-05-19 15:11:05 +02:00
bgamard d428e89c30 #412: process files outside of a transaction 2020-05-19 14:44:41 +02:00
bgamard 9b2aeb7480 add temporary logs 2020-05-19 14:05:34 +02:00
bgamard d9ad69c7ff add more log to debug processing indicator 2020-05-17 21:58:13 +02:00
bgamard 16fc058264 Merge remote-tracking branch 'origin/master' 2020-05-17 21:00:30 +02:00
bgamard 520b143165 #412: better handle concurrent updates and async listeners 2020-05-17 21:00:01 +02:00
cadast 95c37a03f8
Improve Inbox Scanning (#407)
closes #386: delete emails after import + closes #405: auto tag documents imported by email
2020-05-14 13:59:11 +02:00
bgamard 0d058b9c9c at least 2 threads for background work 2020-05-07 11:09:11 +02:00
bgamard 7c72b5e69b #401: importer: truncate document title to allowed size 2020-05-06 13:56:45 +02:00
bgamard 3ec254e908 #400: limit async bus to (cpu cores / 2) threads 2020-05-06 11:35:45 +02:00
bgamard fda13c004e Merge remote-tracking branch 'origin/master' 2020-04-21 21:13:54 +02:00
bgamard 3af85eeea6 bump importer version 2020-04-21 21:13:20 +02:00
lord-lawnmower c08616e6df
Latvian Language Support (#390) 2020-04-13 21:04:47 +02:00
Antti Tapio 7faa0f8a54
Finnish and Swedish language support (#389) 2020-04-12 18:27:06 +02:00
bgamard 26c5fe2e69 next dev iteration 2020-03-26 20:06:58 +01:00
bgamard 6bdaa8352b update README.md 2020-03-26 20:00:44 +01:00
bgamard 6367a1fd15 v1.8 2020-03-26 19:57:10 +01:00
bgamard 2c5ff64d42 Closes #387: validation username and group name in UI 2020-03-25 19:02:50 +01:00
bgamard e614cb41d8 update feedback api url 2020-03-25 18:13:49 +01:00
bgamard 82737e2280 Closes #334: highlight previously opened file 2020-03-07 17:56:01 +01:00
bgamard 3b5c27096b Closes #350: better relations widget 2020-03-07 17:46:40 +01:00
bgamard 8a85830bd3 #381: date fields manually editable 2020-03-07 00:36:03 +01:00
bgamard 19ac90688e upgrade guava 2020-02-19 20:47:26 +01:00
bgamard 5f4a6bc462 #336: fix search by type 2020-02-19 18:00:13 +01:00
bgamard 4c7f3166d4 Closes #332: tag text color legibility 2020-02-15 23:00:35 +01:00
bgamard 4233f4dd88 Closes #317: edit tag color hex code manually 2020-02-15 22:38:06 +01:00
bgamard bd09312418 Closes #336: search document by file mime type 2020-02-15 22:05:04 +01:00
bgamard 11ab07b238 Closes #333: fix overflow in document table with a lot of tags 2020-02-15 15:44:32 +01:00
bgamard d2e2f089fb update README for build deps 2020-02-14 21:48:45 +01:00
bgamard d619f98de7 Closes #379: spaces and colons not allowed in tag name 2020-02-14 21:40:13 +01:00
bgamard 89228a52dc Closes #378: silence useless log from Jersey 2020-02-14 21:33:34 +01:00
bgamard 90a49efa4a Closes #373: high quality PDF to image conversion before OCR 2020-02-13 17:43:07 +01:00
junpet a7423caeb1
Add Hungarian Language Support (#369)
Add Hungarian language support
2020-01-29 15:44:44 +01:00
bgamard 6f31a2c228 Closes #366: get the private key from the right user when processing files 2020-01-21 12:54:50 +01:00
Benjamin Gamard fc98b0882f Merge remote-tracking branch 'origin/master' 2019-12-28 13:54:45 +01:00
Benjamin Gamard dff05967ea Closes #363: pgsql compatibility for table alias in update queries 2019-12-28 13:53:42 +01:00
Benjamin Gamard ec836a2f9d
Update README.md 2019-10-13 01:24:10 +02:00
Benjamin Gamard 737c85cf00
Update README.md 2019-10-13 01:23:03 +02:00
Benjamin Gamard ff7b07f464
Create FUNDING.yml 2019-10-12 23:05:35 +02:00
Mario Voigt 19422b5afa Fixed some language issue (#352) 2019-08-26 14:19:19 +02:00
Benjamin Gamard 6b93e413b6 #321: remove duplicate contributors 2019-06-03 11:45:38 +02:00
Mario Voigt ab72736bcc Update de.json (#322)
Added missing german translation strings
2019-05-25 18:53:02 +02:00
Benjamin Gamard 38939e5d05 #314: force file content in utf8 2019-05-22 15:36:50 +02:00
Benjamin Gamard 1a90a0e0ad next dev iteration 2019-05-21 16:17:54 +02:00
Benjamin Gamard 7aa9fa4646 v1.7 2019-05-21 15:55:41 +02:00
Benjamin Gamard 82d788c8d3 Closes #300: adjust tests 2019-05-21 15:53:54 +02:00
Benjamin Gamard ab8176efcb #300: custom metadata fields: UI read/write 2019-05-21 15:44:23 +02:00
Benjamin Gamard b4c3e7a928 update Docker image infos 2019-05-20 15:18:08 +02:00
Benjamin Gamard 2db263fb68 #300: custom metadata fields: API read 2019-05-20 15:08:16 +02:00
Benjamin Gamard 5fd4d37972 #300: custom metadata fields: API write 2019-05-17 16:00:03 +02:00
Benjamin Gamard 9b1dbf351a #300: custom metadata fields: UI admin 2019-05-15 14:15:55 +02:00
Benjamin Gamard 4c7c058e0d Merge remote-tracking branch 'origin/master' 2019-05-15 13:34:21 +02:00
Benjamin Gamard f8dc08b02b #300: custom metadata fields: API admin 2019-05-15 13:34:01 +02:00
yosef langer 0e6bc3ce54 Hebrew Language Support (#320) 2019-05-11 16:26:01 +02:00
Jean-Marc Tremeaux 94252de73f Merge remote-tracking branch 'origin/master' 2018-10-23 18:16:35 +02:00
Jean-Marc Tremeaux d43072663e Merge remote-tracking branch 'origin/master' 2017-03-21 09:04:20 +01:00
Jean-Marc Tremeaux eb3562567d Fix dockerfile 2017-03-21 08:51:58 +01:00
293 changed files with 10166 additions and 2780 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [jendib]

84
.github/workflows/build-deploy.yml vendored Normal file
View File

@ -0,0 +1,84 @@
name: Maven CI/CD
on:
push:
branches: [master]
tags: [v*]
workflow_dispatch:
jobs:
build_and_publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: "11"
distribution: "temurin"
cache: maven
- name: Install test dependencies
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
- name: Upload war artifact
uses: actions/upload-artifact@v2
with:
name: docs-web-ci.war
path: docs-web/target/docs*.war
build_docker_image:
name: Publish to Docker Hub
runs-on: ubuntu-latest
needs: [build_and_publish]
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Download war artifact
uses: actions/download-artifact@v2
with:
name: docs-web-ci.war
path: docs-web/target
-
name: Setup up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Populate Docker metadata
id: metadata
uses: docker/metadata-action@v3
with:
images: sismics/docs
flavor: |
latest=false
tags: |
type=ref,event=tag
type=raw,value=latest,enable=${{ github.ref_type != 'tag' }}
labels: |
org.opencontainers.image.title = Teedy
org.opencontainers.image.description = Teedy is an open source, lightweight document management system for individuals and businesses.
org.opencontainers.image.created = ${{ github.event_created_at }}
org.opencontainers.image.author = Sismics
org.opencontainers.image.url = https://teedy.io/
org.opencontainers.image.vendor = Sismics
org.opencontainers.image.license = GPLv2
org.opencontainers.image.version = ${{ github.event_head_commit.id }}
-
name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}

11
.gitignore vendored
View File

@ -11,6 +11,11 @@
*.iml
node_modules
import_test
docs-importer-linux
docs-importer-macos
docs-importer-win.exe
teedy-importer-linux
teedy-importer-macos
teedy-importer-win.exe
docs/*
!docs/.gitkeep
#macos
.DS_Store

View File

@ -1,26 +0,0 @@
sudo: required
dist: trusty
language: java
before_install:
- sudo add-apt-repository -y ppa:mc3man/trusty-media
- sudo apt-get -qq update
- sudo apt-get -y -q install ffmpeg mediainfo tesseract-ocr tesseract-ocr-fra tesseract-ocr-ita tesseract-ocr-kor tesseract-ocr-rus tesseract-ocr-ukr tesseract-ocr-spa tesseract-ocr-ara tesseract-ocr-hin tesseract-ocr-deu tesseract-ocr-pol tesseract-ocr-jpn tesseract-ocr-por tesseract-ocr-tha tesseract-ocr-jpn tesseract-ocr-chi-sim tesseract-ocr-chi-tra tesseract-ocr-nld tesseract-ocr-tur
- sudo apt-get -y -q install haveged && sudo service haveged start
after_success:
- |
if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then
mvn -Pprod -DskipTests clean install
docker login -u $DOCKER_USER -p $DOCKER_PASS
export REPO=sismics/docs
export TAG=`if [ "$TRAVIS_BRANCH" == "master" ]; then echo "latest"; else echo $TRAVIS_BRANCH ; fi`
docker build -f Dockerfile -t $REPO:$COMMIT .
docker tag $REPO:$COMMIT $REPO:$TAG
docker tag $REPO:$COMMIT $REPO:travis-$TRAVIS_BUILD_NUMBER
docker push $REPO
fi
env:
global:
- secure: LRGpjWORb0qy6VuypZjTAfA8uRHlFUMTwb77cenS9PPRBxuSnctC531asS9Xg3DqC5nsRxBBprgfCKotn5S8nBSD1ceHh84NASyzLSBft3xSMbg7f/2i7MQ+pGVwLncusBU6E/drnMFwZBleo+9M8Tf96axY5zuUp90MUTpSgt0=
- secure: bCDDR6+I7PmSkuTYZv1HF/z98ANX/SFEESUCqxVmV5Gs0zFC0vQXaPJQ2xaJNRop1HZBFMZLeMMPleb0iOs985smpvK2F6Rbop9Tu+Vyo0uKqv9tbZ7F8Nfgnv9suHKZlL84FNeUQZJX6vsFIYPEJ/r7K5P/M0PdUy++fEwxEhU=
- secure: ewXnzbkgCIHpDWtaWGMa1OYZJ/ki99zcIl4jcDPIC0eB3njX/WgfcC6i0Ke9mLqDqwXarWJ6helm22sNh+xtQiz6isfBtBX+novfRt9AANrBe3koCMUemMDy7oh5VflBaFNP0DVb8LSCnwf6dx6ZB5E9EB8knvk40quc/cXpGjY=
- COMMIT=${TRAVIS_COMMIT::8}

View File

@ -1,11 +1,48 @@
FROM sismics/ubuntu-jetty:9.4.12-2
MAINTAINER b.gamard@sismics.com
FROM sismics/ubuntu-jetty:11.0.14
LABEL maintainer="b.gamard@sismics.com"
RUN apt-get update && apt-get -y -q install ffmpeg mediainfo tesseract-ocr tesseract-ocr-fra tesseract-ocr-ita tesseract-ocr-kor tesseract-ocr-rus tesseract-ocr-ukr tesseract-ocr-spa tesseract-ocr-ara tesseract-ocr-hin tesseract-ocr-deu tesseract-ocr-pol tesseract-ocr-jpn tesseract-ocr-por tesseract-ocr-tha tesseract-ocr-jpn tesseract-ocr-chi-sim tesseract-ocr-chi-tra tesseract-ocr-nld tesseract-ocr-tur && \
apt-get clean && rm -rf /var/lib/apt/lists/*
RUN apt-get update && \
apt-get -y -q --no-install-recommends install \
ffmpeg \
mediainfo \
tesseract-ocr \
tesseract-ocr-ara \
tesseract-ocr-ces \
tesseract-ocr-chi-sim \
tesseract-ocr-chi-tra \
tesseract-ocr-dan \
tesseract-ocr-deu \
tesseract-ocr-fin \
tesseract-ocr-fra \
tesseract-ocr-heb \
tesseract-ocr-hin \
tesseract-ocr-hun \
tesseract-ocr-ita \
tesseract-ocr-jpn \
tesseract-ocr-kor \
tesseract-ocr-lav \
tesseract-ocr-nld \
tesseract-ocr-nor \
tesseract-ocr-pol \
tesseract-ocr-por \
tesseract-ocr-rus \
tesseract-ocr-spa \
tesseract-ocr-swe \
tesseract-ocr-tha \
tesseract-ocr-tur \
tesseract-ocr-ukr \
tesseract-ocr-vie \
tesseract-ocr-sqi && \
apt-get clean && rm -rf /var/lib/apt/lists/* && \
mkdir /app && \
cd /app && \
java -jar /opt/jetty/start.jar --add-modules=server,http,webapp,deploy
# Remove the embedded javax.mail jar from Jetty
RUN rm -f /opt/jetty/lib/mail/javax.mail.glassfish-*.jar
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"]
ADD docs.xml /opt/jetty/webapps/docs.xml
ADD docs-web/target/docs-web-*.war /opt/jetty/webapps/docs.war

204
README.md
View File

@ -2,40 +2,38 @@
<img src="https://teedy.io/img/github-title.png" alt="Teedy" width=500 />
</h3>
[![Twitter: @teedyio](https://img.shields.io/badge/contact-@teedyio-blue.svg?style=flat)](https://twitter.com/teedyio)
[![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-blue.svg)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
[![Build Status](https://secure.travis-ci.org/sismics/docs.png)](http://travis-ci.org/sismics/docs)
[![Maven CI/CD](https://github.com/sismics/docs/actions/workflows/build-deploy.yml/badge.svg)](https://github.com/sismics/docs/actions/workflows/build-deploy.yml)
Teedy is an open source, lightweight document management system for individuals and businesses.
**Discuss it on [Product Hunt](https://www.producthunt.com/posts/sismics-docs) 🦄**
<hr />
<h2 align="center">
Sismics Docs is now called Teedy! You can still find our cloud and support offer on <a href="https://teedy.io">teedy.io</a>
<a href="https://github.com/users/jendib/sponsorship">Sponsor this project if you use and appreciate it!</a>
</h2>
<hr />
![New!](https://teedy.io/img/laptop-demo.png?20180301)
Demo
----
# Demo
A demo is available at [demo.teedy.io](https://demo.teedy.io)
- Guest login is enabled with read access on all documents
- "admin" login with "admin" password
- "demo" login with "password" password
Features
--------
# Features
- Responsive user interface
- Optical character recognition
- LDAP authentication ![New!](https://www.sismics.com/public/img/new.png)
- Support image, PDF, ODT, DOCX, PPTX files
- Video file support
- Flexible search engine with suggestions and highlighting
- Full text search in all supported files
- All [Dublin Core](http://dublincore.org/) metadata
- Custom user-defined metadata ![New!](https://www.sismics.com/public/img/new.png)
- Workflow system ![New!](https://www.sismics.com/public/img/new.png)
- 256-bit AES encryption of stored files
- File versioning ![New!](https://www.sismics.com/public/img/new.png)
@ -55,86 +53,192 @@ Features
- [Bulk files importer](https://github.com/sismics/docs/tree/master/docs-importer) (single or scan mode)
- Tested to one million documents
Install with Docker
-------------------
# Install with Docker
A preconfigured Docker image is available, including OCR and media conversion tools, listening on port 8080. If no PostgreSQL config is provided, the database is an embedded H2 database. The H2 embedded database should only be used for testing. For production usage use the provided PostgreSQL configuration (check the Docker Compose example)
From a Docker host, run this command to download and install Teedy. The server will run on <http://[your-docker-host-ip]:8100>.
**The default admin password is "admin". Don't forget to change it before going to production.**
docker run --rm --name teedy_latest -d -e DOCS_BASE_URL='http://[your-docker-host-ip]:8100' -p 8100:8080 -v teedy_latest:/data sismics/docs:latest
<img src="http://www.newdesignfile.com/postpic/2011/01/green-info-icon_206509.png" width="16px" height="16px"> **Note:** You will need to change [your-docker-host-ip] with the IP address or FQDN of your docker host e.g.
FQDN: http://docs.mycompany.com
IP: http://192.168.100.10
- Master branch, can be unstable. Not recommended for production use: `sismics/docs:latest`
- Latest stable version: `sismics/docs:v1.11`
Manual installation
-------------------
The data directory is `/data`. Don't forget to mount a volume on it.
#### Requirements
- Java 8 with the [Java Cryptography Extension](http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html)
- Tesseract 3 or 4 for OCR
To build external URL, the server is expecting a `DOCS_BASE_URL` environment variable (for example https://teedy.mycompany.com)
## Available environment variables
- General
- `DOCS_BASE_URL`: The base url used by the application. Generated url's will be using this as base.
- `DOCS_GLOBAL_QUOTA`: Defines the default quota applying to all users.
- `DOCS_BCRYPT_WORK`: Defines the work factor which is used for password hashing. The default is `10`. This value may be `4...31` including `4` and `31`. The specified value will be used for all new users and users changing their password. Be aware that setting this factor to high can heavily impact login and user creation performance.
- Admin
- `DOCS_ADMIN_EMAIL_INIT`: Defines the e-mail-address the admin user should have upon initialization.
- `DOCS_ADMIN_PASSWORD_INIT`: Defines the password the admin user should have upon initialization. Needs to be a bcrypt hash. **Be aware that `$` within the hash have to be escaped with a second `$`.**
- Database
- `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.
- Language
- `DOCS_DEFAULT_LANGUAGE`: The language which will be used as default. Currently supported values are:
- `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`
- E-Mail
- `DOCS_SMTP_HOSTNAME`: Hostname of the SMTP-Server to be used by Teedy.
- `DOCS_SMTP_PORT`: The port which should be used.
- `DOCS_SMTP_USERNAME`: The username to be used.
- `DOCS_SMTP_PASSWORD`: The password to be used.
## Examples
In the following examples some passwords are exposed in cleartext. This was done in order to keep the examples simple. We strongly encourage you to use variables with an `.env` file or other means to securely store your passwords.
### Default, using PostgreSQL
```yaml
version: '3'
services:
# Teedy Application
teedy-server:
image: sismics/docs:v1.11
restart: unless-stopped
ports:
# Map internal port to host
- 8080:8080
environment:
# Base url to be used
DOCS_BASE_URL: "https://docs.example.com"
# Set the admin email
DOCS_ADMIN_EMAIL_INIT: "admin@example.com"
# Set the admin password (in this example: "superSecure")
DOCS_ADMIN_PASSWORD_INIT: "$$2a$$05$$PcMNUbJvsk7QHFSfEIDaIOjk1VI9/E7IPjTKx.jkjPxkx2EOKSoPS"
# Setup the database connection. "teedy-db" is the hostname
# and "teedy" is the name of the database the application
# will connect to.
DATABASE_URL: "jdbc:postgresql://teedy-db:5432/teedy"
DATABASE_USER: "teedy_db_user"
DATABASE_PASSWORD: "teedy_db_password"
volumes:
- ./docs/data:/data
networks:
- docker-internal
- internet
depends_on:
- teedy-db
# DB for Teedy
teedy-db:
image: postgres:13.1-alpine
restart: unless-stopped
expose:
- 5432
environment:
POSTGRES_USER: "teedy_db_user"
POSTGRES_PASSWORD: "teedy_db_password"
POSTGRES_DB: "teedy"
volumes:
- ./docs/db:/var/lib/postgresql/data
networks:
- docker-internal
networks:
# Network without internet access. The db does not need
# access to the host network.
docker-internal:
driver: bridge
internal: true
internet:
driver: bridge
```
### Using the internal database (only for testing)
```yaml
version: '3'
services:
# Teedy Application
teedy-server:
image: sismics/docs:v1.11
restart: unless-stopped
ports:
# Map internal port to host
- 8080:8080
environment:
# Base url to be used
DOCS_BASE_URL: "https://docs.example.com"
# Set the admin email
DOCS_ADMIN_EMAIL_INIT: "admin@example.com"
# Set the admin password (in this example: "superSecure")
DOCS_ADMIN_PASSWORD_INIT: "$$2a$$05$$PcMNUbJvsk7QHFSfEIDaIOjk1VI9/E7IPjTKx.jkjPxkx2EOKSoPS"
volumes:
- ./docs/data:/data
```
# Manual installation
## Requirements
- Java 11
- Tesseract 4 for OCR
- ffmpeg for video thumbnails
- mediainfo for video metadata extraction
- A webapp server like [Jetty](http://eclipse.org/jetty/) or [Tomcat](http://tomcat.apache.org/)
#### Download
## Download
The latest release is downloadable here: <https://github.com/sismics/docs/releases> in WAR format.
**The default admin password is "admin". Don't forget to change it before going to production.**
How to build Teedy from the sources
----------------------------------
## How to build Teedy from the sources
Prerequisites: JDK 8 with JCE, Maven 3, Tesseract 3 or 4
Prerequisites: JDK 11, Maven 3, NPM, Grunt, Tesseract 4
Teedy is organized in several Maven modules:
- docs-core
- docs-web
- docs-web-common
- docs-core
- docs-web
- docs-web-common
First off, clone the repository: `git clone git://github.com/sismics/docs.git`
or download the sources from GitHub.
#### Launch the build
### Launch the build
From the root directory:
mvn clean -DskipTests install
```console
mvn clean -DskipTests install
```
#### Run a stand-alone version
### Run a stand-alone version
From the `docs-web` directory:
mvn jetty:run
```console
mvn jetty:run
```
#### Build a .war to deploy to your servlet container
### Build a .war to deploy to your servlet container
From the `docs-web` directory:
mvn -Pprod -DskipTests clean install
```console
mvn -Pprod -DskipTests clean install
```
You will get your deployable WAR in the `docs-web/target` directory.
Contributing
------------
# Contributing
All contributions are more than welcomed. Contributions may close an issue, fix a bug (reported or not reported), improve the existing code, add new feature, and so on.
The `master` branch is the default and base branch for the project. It is used for development and all Pull Requests should go there.
Community
---------
Get updates on Teedy's development and chat with the project maintainers:
- Follow [@teedyio on Twitter](https://twitter.com/teedyio)
- Read and subscribe to [The Official Teedy Blog](https://blog.teedy.io/)
- Check the [Official Website](https://teedy.io)
- Join us [on Facebook](https://www.facebook.com/teedyio)
License
-------
# License
Teedy is released under the terms of the GPL license. See `COPYING` for more
information or see <http://opensource.org/licenses/GPL-2.0>.

18
docker-compose.yml Normal file
View File

@ -0,0 +1,18 @@
version: '3'
services:
# Teedy Application
teedy-server:
image: sismics/docs:v1.10
restart: unless-stopped
ports:
# Map internal port to host
- 8080:8080
environment:
# Base url to be used
DOCS_BASE_URL: "https://docs.example.com"
# Set the admin email
DOCS_ADMIN_EMAIL_INIT: "admin@example.com"
# Set the admin password (in this example: "superSecure")
DOCS_ADMIN_PASSWORD_INIT: "$$2a$$05$$PcMNUbJvsk7QHFSfEIDaIOjk1VI9/E7IPjTKx.jkjPxkx2EOKSoPS"
volumes:
- ./docs/data:/data

View File

@ -4,7 +4,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.0'
classpath 'com.android.tools.build:gradle:3.4.0'
}
}
apply plugin: 'com.android.application'

View File

@ -34,6 +34,7 @@ public class LanguageAdapter extends BaseAdapter {
languageList.add(new Language("fra", R.string.language_french, R.drawable.fra));
languageList.add(new Language("eng", R.string.language_english, R.drawable.eng));
languageList.add(new Language("deu", R.string.language_german, R.drawable.deu));
languageList.add(new Language("pol", R.string.language_polish, R.drawable.pol));
}
@Override

View File

@ -39,7 +39,9 @@ public class SearchQueryBuilder {
*/
public SearchQueryBuilder simpleSearch(String simpleSearch) {
if (isValid(simpleSearch)) {
query.append(SEARCH_SEPARATOR).append(simpleSearch);
query.append(SEARCH_SEPARATOR)
.append("simple:")
.append(simpleSearch);
}
return this;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 B

View File

@ -0,0 +1,164 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Validation -->
<string name="validate_error_email">Nieprawidłowy email</string>
<string name="validate_error_length_min">Za krótki (min. %d)</string>
<string name="validate_error_length_max">Za długi (max. %d)</string>
<string name="validate_error_required">Wymagany</string>
<string name="validate_error_alphanumeric">Tylko litery i cyfry</string>
<!-- App -->
<string name="app_name" translatable="false">Teedy</string>
<string name="drawer_open">Otwórz szufladę nawigacji</string>
<string name="drawer_close">Zamknij szufladę nawigacji</string>
<string name="login_explain"><![CDATA[Aby rozpocząć, musisz pobrać i zainstalować serwer Teedy na <a href="https://github.com/sismics/docs">github.com/sismics/docs</a> i poniżej wprowadzić adres]]></string>
<string name="server">Serwer</string>
<string name="username">Użytkownik</string>
<string name="password">Hasło</string>
<string name="login">Zaloguj</string>
<string name="ok">OK</string>
<string name="cancel">Anuluj</string>
<string name="login_fail_title">Błąd logowania</string>
<string name="login_fail">Nieprawidłowa nazwa użytkownika lub hasło</string>
<string name="network_error_title">Błąd sieci</string>
<string name="network_error">Błąd sieci, sprawdź połączenie z interneterm oraz adres URL serwera</string>
<string name="invalid_url_title">Nieprawidłowy adres URL</string>
<string name="invalid_url">Sprawdź adres URL serwera i spróbuj ponownie</string>
<string name="crash_toast_text">Wystąpiła awaria, wysłano raport w celu rozwiązania tego problemu</string>
<string name="created_date">Data utworzenia</string>
<string name="download_file">Pobierz bieżący plik</string>
<string name="download_document">Pobierz</string>
<string name="action_search">Znadź dokumenty</string>
<string name="all_documents">Wszystkie dokumenty</string>
<string name="shared_documents">Udostępnione dokumenty</string>
<string name="all_tags">Wszystkie etykiety</string>
<string name="no_tags">Brak etykiet</string>
<string name="error_loading_tags">Błąd ładowania etykiet</string>
<string name="no_documents">Brak dokumentów</string>
<string name="error_loading_documents">Błąd ładowania dokumentów</string>
<string name="no_files">Brak plików</string>
<string name="error_loading_files">Błąd ładowania plików</string>
<string name="new_document">Nowy dokument</string>
<string name="share">Udostępnij</string>
<string name="close">Zamknij</string>
<string name="add">Dodaj</string>
<string name="add_share_hint">Nazwa udostępnienia (opcjonalnie)</string>
<string name="document_not_shared">Ten dokument nie jest obecnie udostępniony</string>
<string name="delete_share">Usuń udostępnienie</string>
<string name="send_share">Wyślij link udostępnienia</string>
<string name="error_loading_shares">Błąd ładowania udostępnień</string>
<string name="error_adding_share">Błąd dodawania udostępnienia</string>
<string name="share_default_name">Udostępnij link</string>
<string name="error_deleting_share">Błąd usuwania udostępnienia</string>
<string name="send_share_to">Wyślij link udostępnienia do</string>
<string name="upload_file">dodaj plik</string>
<string name="upload_from">Przeslij plik z</string>
<string name="settings">ustawienia</string>
<string name="logout">Wyloguj</string>
<string name="version">Wersja</string>
<string name="build">Kompilacja</string>
<string name="pref_advanced_category">Ustawienia zaawansowane</string>
<string name="pref_about_category">O programie</string>
<string name="pref_github">GitHub</string>
<string name="pref_issue">Zgłoś błąd</string>
<string name="pref_clear_cache_title">Wyczyść cache</string>
<string name="pref_clear_cache_summary">Wyczyść podręczne pliki</string>
<string name="pref_clear_cache_success">Cache wyczyszczony</string>
<string name="pref_clear_history_title">Wyczyść historię wyszukiwania</string>
<string name="pref_clear_history_summary">Opróżnij ostatnie sugestie wyszukiwania</string>
<string name="pref_clear_history_success">Historia wyszukiwania wyczyszczona</string>
<string name="pref_cache_size">Rozmiar cache</string>
<string name="language_french" translatable="false">Francuski</string>
<string name="language_english" translatable="false">Angielski</string>
<string name="language_german" translatable="false">Niemiecki</string>
<string name="language_polish" translatable="false">Polski</string>
<string name="save">Zapisz</string>
<string name="edit_document">Edytuj</string>
<string name="error_editing_document">Błąd sieci, spróbuj ponownie</string>
<string name="please_wait">Proszę czekać</string>
<string name="document_editing_message">Wysyłam twoje dane</string>
<string name="delete_document">Usuń</string>
<string name="delete_document_title">Usuń dokument</string>
<string name="delete_document_message">Naprawdę chcesz usunąć dokument i powiązane z nim pliki?</string>
<string name="document_delete_failure">Błąd sieci w czasie usuwania tego dokumentu</string>
<string name="document_deleting_message">Usuwanie dokumentu</string>
<string name="delete_file_title">Usuń plik</string>
<string name="delete_file_message">Naprawdę chcesz usunąć ten plik?</string>
<string name="file_delete_failure">Błąd sieci w czasie usuwania bieżącego pliku</string>
<string name="file_deleting_message">Usuwanie pliku</string>
<string name="error_reading_file">Błąd podczas odczytu pliku</string>
<string name="upload_notification_title">Teedy</string>
<string name="upload_notification_message">Przesyłanie nowego pliku do dokumentu</string>
<string name="upload_notification_error">Błąd przsyłania nowego pliku</string>
<string name="delete_file">Usuń bieżący plik</string>
<string name="advanced_search">Zaawansowane wyszukiwanie</string>
<string name="search">Znajdź</string>
<string name="add_tags">Dodaj eytkiety</string>
<string name="creation_date">Data utworzenia</string>
<string name="description">Opis</string>
<string name="title">Tytuł</string>
<string name="simple_search">Proste wyszukiwanie</string>
<string name="fulltext_search">Wyszukiwanie pełnotekstowe</string>
<string name="creator">Autor</string>
<string name="after_date">Po dacie</string>
<string name="before_date">Przed datą</string>
<string name="search_tags">Znajdź etykiety</string>
<string name="all_languages">Wszystkie języki</string>
<string name="toggle_informations">Przełącz informacje</string>
<string name="who_can_access">Kto ma dostęp</string>
<string name="comments">Komentarze</string>
<string name="no_comments">Brak komentarzy</string>
<string name="error_loading_comments">Błąd ładowania komentarzy</string>
<string name="send">Wyślij</string>
<string name="add_comment">Dodaj komentarz</string>
<string name="comment_add_failure">Błąd dodawania komentarza</string>
<string name="adding_comment">Dodawanie komentarza</string>
<string name="comment_delete">Usuń komentarz</string>
<string name="deleting_comment">Usuwanie komentarza</string>
<string name="error_deleting_comment">Błąd usuwania komentarza</string>
<string name="export_pdf">PDF</string>
<string name="download">Pobierz</string>
<string name="margin">Margines</string>
<string name="fit_image_to_page">Dostosuj obraz do strony</string>
<string name="export_comments">Eksport komentarzy</string>
<string name="export_metadata">Eksport metadanych</string>
<string name="mm">mm</string>
<string name="download_file_title">Eksport plików Teedy</string>
<string name="download_document_title">Eksport dokumentu Teedy</string>
<string name="download_pdf_title">Eksport Teedy jako PDF</string>
<string name="latest_activity">Ostatnie aktywności</string>
<string name="activity">Aktywności</string>
<string name="email">E-mail</string>
<string name="storage_quota">Limit magazynu</string>
<string name="storage_display">%1$d/%2$d MB</string>
<string name="validation_code">Kod weryfikujący</string>
<string name="shared">Udostępnienie</string>
<string name="language">Język</string>
<string name="coverage">Zakres</string>
<string name="type">Rodzaj</string>
<string name="source">Źródło</string>
<string name="format">Format</string>
<string name="publisher">Udostępniający</string>
<string name="identifier">Identifikator</string>
<string name="subject">temat</string>
<string name="rights">Prawa</string>
<string name="contributors">Współtwórcy</string>
<string name="relations">Powiązania</string>
<!-- Audit log -->
<string name="auditlog_Acl">ACL</string>
<string name="auditlog_Comment">Komentarz</string>
<string name="auditlog_Document">Dokument</string>
<string name="auditlog_File">Plik</string>
<string name="auditlog_Group">Grupa</string>
<string name="auditlog_Route">Przepływ</string>
<string name="auditlog_RouteModel">Model przepływu</string>
<string name="auditlog_Tag">Etykieta</string>
<string name="auditlog_User">Użytkownik</string>
<string name="auditlog_Webhook">Webhook</string>
<string name="auditlog_created">utworzony</string>
<string name="auditlog_updated">zaktualizowany</string>
<string name="auditlog_deleted">usunięty</string>
</resources>

View File

@ -72,6 +72,7 @@
<string name="language_french" translatable="false">Français</string>
<string name="language_english" translatable="false">English</string>
<string name="language_german" translatable="false">Deutsch</string>
<string name="language_polish" translatable="false">Polski</string>
<string name="save">Save</string>
<string name="edit_document">Edit</string>
<string name="error_editing_document">Network error, please try again</string>

View File

@ -1,6 +1,6 @@
#Wed Jan 30 16:31:31 CET 2019
#Tue May 07 11:49:13 CEST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip

View File

@ -5,8 +5,8 @@
<parent>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<version>1.6-SNAPSHOT</version>
<relativePath>..</relativePath>
<version>1.12-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -18,17 +18,7 @@
<!-- Persistence layer dependencies -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<artifactId>hibernate-core-jakarta</artifactId>
</dependency>
<!-- Other external dependencies -->
@ -48,8 +38,8 @@
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
@ -63,8 +53,8 @@
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
</dependency>
<dependency>
@ -91,10 +81,10 @@
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
<groupId>at.favre.lib</groupId>
<artifactId>bcrypt</artifactId>
</dependency>
<dependency>
@ -122,16 +112,16 @@
<artifactId>lucene-highlighter</artifactId>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>org.apache.directory.api</groupId>
<artifactId>api-all</artifactId>
</dependency>
<!-- Only there to read old index and rebuild them -->
<dependency>
<groupId>org.apache.lucene</groupId>
@ -189,7 +179,7 @@
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>junit</groupId>

View File

@ -40,7 +40,25 @@ public enum ConfigType {
INBOX_ENABLED,
INBOX_HOSTNAME,
INBOX_PORT,
INBOX_STARTTLS,
INBOX_USERNAME,
INBOX_PASSWORD,
INBOX_TAG
INBOX_FOLDER,
INBOX_TAG,
INBOX_AUTOMATIC_TAGS,
INBOX_DELETE_IMPORTED,
/**
* LDAP connection.
*/
LDAP_ENABLED,
LDAP_HOST,
LDAP_PORT,
LDAP_USESSL,
LDAP_ADMIN_DN,
LDAP_ADMIN_PASSWORD,
LDAP_BASE_DN,
LDAP_FILTER,
LDAP_DEFAULT_EMAIL,
LDAP_DEFAULT_STORAGE
}

View File

@ -18,13 +18,18 @@ public class Constants {
/**
* Administrator's default password ("admin").
*/
public static final String DEFAULT_ADMIN_PASSWORD = "$2a$05$6Ny3TjrW3aVAL1or2SlcR.fhuDgPKp5jp.P9fBXwVNePgeLqb4i3C";
public static final String DEFAULT_ADMIN_PASSWORD = "$2y$10$xg0EEKVUehutDI1m6qQhVeFz7SMQMl1jQzjf2KkVsR2c7aV2vyyjK";
/**
* Administrator's default email.
*/
public static final String DEFAULT_ADMIN_EMAIL = "admin@localhost";
/**
* Bcrypt default work factor
*/
public static final int DEFAULT_BCRYPT_WORK = 10;
/**
* Guest user ID.
*/
@ -38,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");
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.
@ -73,6 +78,11 @@ public class Constants {
*/
public static final String ADMIN_EMAIL_INIT_ENV = "DOCS_ADMIN_EMAIL_INIT";
/**
* Work factor to be used by Bcrypt
*/
public static final String BCRYPT_WORK_ENV = "DOCS_BCRYPT_WORK";
/**
* Expiration time of the password recovery in hours.
*/

View File

@ -0,0 +1,14 @@
package com.sismics.docs.core.constant;
/**
* Metadata type.
*
* @author bgamard
*/
public enum MetadataType {
STRING,
INTEGER,
FLOAT,
DATE,
BOOLEAN
}

View File

@ -10,8 +10,8 @@ import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.docs.core.util.SecurityUtil;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@ -128,6 +128,9 @@ public class AclDao {
if (SecurityUtil.skipAclCheck(targetIdList)) {
return true;
}
if (targetIdList.isEmpty()) {
return false;
}
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select a.ACL_ID_C from T_ACL a ");

View File

@ -12,7 +12,7 @@ import com.sismics.docs.core.util.jpa.QueryParam;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import jakarta.persistence.EntityManager;
import java.sql.Timestamp;
import java.util.*;

View File

@ -4,8 +4,8 @@ import com.sismics.docs.core.model.jpa.AuthenticationToken;
import com.sismics.util.context.ThreadLocalContext;
import org.joda.time.DateTime;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import java.util.Date;
import java.util.List;
import java.util.UUID;

View File

@ -6,9 +6,9 @@ import com.sismics.docs.core.model.jpa.Comment;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import jakarta.persistence.EntityManager;
import jakarta.persistence.NoResultException;
import jakarta.persistence.Query;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
@ -27,7 +27,6 @@ public class CommentDao {
* @param comment Comment
* @param userId User ID
* @return New ID
* @throws Exception
*/
public String create(Comment comment, String userId) {
// Create the UUID
@ -99,7 +98,7 @@ public class CommentDao {
@SuppressWarnings("unchecked")
List<Object[]> l = q.getResultList();
List<CommentDto> commentDtoList = new ArrayList<CommentDto>();
List<CommentDto> commentDtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
CommentDto commentDto = new CommentDto();
@ -107,7 +106,7 @@ public class CommentDao {
commentDto.setContent((String) o[i++]);
commentDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
commentDto.setCreatorName((String) o[i++]);
commentDto.setCreatorEmail((String) o[i++]);
commentDto.setCreatorEmail((String) o[i]);
commentDtoList.add(commentDto);
}
return commentDtoList;

View File

@ -4,8 +4,8 @@ import com.sismics.docs.core.constant.ConfigType;
import com.sismics.docs.core.model.jpa.Config;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import jakarta.persistence.EntityManager;
import jakarta.persistence.NoResultException;
/**
* Configuration parameter DAO.

View File

@ -4,8 +4,8 @@ import com.sismics.docs.core.dao.dto.ContributorDto;
import com.sismics.docs.core.model.jpa.Contributor;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@ -56,7 +56,7 @@ public class ContributorDao {
@SuppressWarnings("unchecked")
public List<ContributorDto> getByDocumentId(String documentId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select u.USE_USERNAME_C, u.USE_EMAIL_C from T_CONTRIBUTOR c ");
StringBuilder sb = new StringBuilder("select distinct u.USE_USERNAME_C, u.USE_EMAIL_C from T_CONTRIBUTOR c ");
sb.append(" join T_USER u on u.USE_ID_C = c.CTR_IDUSER_C ");
sb.append(" where c.CTR_IDDOC_C = :documentId ");
Query q = em.createNativeQuery(sb.toString());

View File

@ -7,9 +7,10 @@ import com.sismics.docs.core.model.jpa.Document;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import jakarta.persistence.EntityManager;
import jakarta.persistence.NoResultException;
import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import java.sql.Timestamp;
import java.util.Date;
import java.util.List;
@ -50,10 +51,9 @@ public class DocumentDao {
* @param limit Limit
* @return List of documents
*/
@SuppressWarnings("unchecked")
public List<Document> findAll(int offset, int limit) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select d from Document d where d.deleteDate is null");
TypedQuery<Document> q = em.createQuery("select d from Document d where d.deleteDate is null", Document.class);
q.setFirstResult(offset);
q.setMaxResults(limit);
return q.getResultList();
@ -65,10 +65,9 @@ public class DocumentDao {
* @param userId User ID
* @return List of documents
*/
@SuppressWarnings("unchecked")
public List<Document> findByUserId(String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select d from Document d where d.userId = :userId and d.deleteDate is null");
TypedQuery<Document> q = em.createQuery("select d from Document d where d.userId = :userId and d.deleteDate is null", Document.class);
q.setParameter("userId", userId);
return q.getResultList();
}
@ -138,16 +137,16 @@ public class DocumentDao {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the document
Query q = em.createQuery("select d from Document d where d.id = :id and d.deleteDate is null");
q.setParameter("id", id);
Document documentDb = (Document) q.getSingleResult();
TypedQuery<Document> dq = em.createQuery("select d from Document d where d.id = :id and d.deleteDate is null", Document.class);
dq.setParameter("id", id);
Document documentDb = dq.getSingleResult();
// Delete the document
Date dateNow = new Date();
documentDb.setDeleteDate(dateNow);
// Delete linked data
q = em.createQuery("update File f set f.deleteDate = :dateNow where f.documentId = :documentId and f.deleteDate is null");
Query q = em.createQuery("update File f set f.deleteDate = :dateNow where f.documentId = :documentId and f.deleteDate is null");
q.setParameter("documentId", id);
q.setParameter("dateNow", dateNow);
q.executeUpdate();
@ -179,10 +178,10 @@ public class DocumentDao {
*/
public Document getById(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select d from Document d where d.id = :id and d.deleteDate is null");
TypedQuery<Document> q = em.createQuery("select d from Document d where d.id = :id and d.deleteDate is null", Document.class);
q.setParameter("id", id);
try {
return (Document) q.getSingleResult();
return q.getSingleResult();
} catch (NoResultException e) {
return null;
}
@ -199,9 +198,9 @@ public class DocumentDao {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the document
Query q = em.createQuery("select d from Document d where d.id = :id and d.deleteDate is null");
TypedQuery<Document> q = em.createQuery("select d from Document d where d.id = :id and d.deleteDate is null", Document.class);
q.setParameter("id", document.getId());
Document documentDb = (Document) q.getSingleResult();
Document documentDb = q.getSingleResult();
// Update the document
documentDb.setTitle(document.getTitle());
@ -232,12 +231,11 @@ public class DocumentDao {
*/
public void updateFileId(Document document) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query query = em.createNativeQuery("update T_DOCUMENT d set d.DOC_IDFILE_C = :fileId, d.DOC_UPDATEDATE_D = :updateDate where d.DOC_ID_C = :id");
Query query = em.createNativeQuery("update T_DOCUMENT d set DOC_IDFILE_C = :fileId, DOC_UPDATEDATE_D = :updateDate where d.DOC_ID_C = :id");
query.setParameter("updateDate", new Date());
query.setParameter("fileId", document.getFileId());
query.setParameter("id", document.getId());
query.executeUpdate();
}
/**

View File

@ -0,0 +1,89 @@
package com.sismics.docs.core.dao;
import com.sismics.docs.core.constant.MetadataType;
import com.sismics.docs.core.dao.dto.DocumentMetadataDto;
import com.sismics.docs.core.model.jpa.DocumentMetadata;
import com.sismics.util.context.ThreadLocalContext;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* Document metadata DAO.
*
* @author bgamard
*/
public class DocumentMetadataDao {
/**
* Creates a new document metadata.
*
* @param documentMetadata Document metadata
* @return New ID
*/
public String create(DocumentMetadata documentMetadata) {
// Create the UUID
documentMetadata.setId(UUID.randomUUID().toString());
// Create the document metadata
EntityManager em = ThreadLocalContext.get().getEntityManager();
em.persist(documentMetadata);
return documentMetadata.getId();
}
/**
* Updates a document metadata.
*
* @param documentMetadata Document metadata
* @return Updated document metadata
*/
public DocumentMetadata update(DocumentMetadata documentMetadata) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the document metadata
Query q = em.createQuery("select u from DocumentMetadata u where u.id = :id");
q.setParameter("id", documentMetadata.getId());
DocumentMetadata documentMetadataDb = (DocumentMetadata) q.getSingleResult();
// Update the document metadata
documentMetadataDb.setValue(documentMetadata.getValue());
return documentMetadata;
}
/**
* Returns the list of all metadata values on a document.
*
* @param documentId Document ID
* @return List of metadata
*/
@SuppressWarnings("unchecked")
public List<DocumentMetadataDto> getByDocumentId(String documentId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select dm.DME_ID_C, dm.DME_IDDOCUMENT_C, dm.DME_IDMETADATA_C, dm.DME_VALUE_C, m.MET_TYPE_C");
sb.append(" from T_DOCUMENT_METADATA dm, T_METADATA m ");
sb.append(" where dm.DME_IDMETADATA_C = m.MET_ID_C and dm.DME_IDDOCUMENT_C = :documentId and m.MET_DELETEDATE_D is null");
// Perform the search
Query q = em.createNativeQuery(sb.toString());
q.setParameter("documentId", documentId);
List<Object[]> l = q.getResultList();
// Assemble results
List<DocumentMetadataDto> dtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
DocumentMetadataDto dto = new DocumentMetadataDto();
dto.setId((String) o[i++]);
dto.setDocumentId((String) o[i++]);
dto.setMetadataId((String) o[i++]);
dto.setValue((String) o[i++]);
dto.setType(MetadataType.valueOf((String) o[i]));
dtoList.add(dto);
}
return dtoList;
}
}

View File

@ -4,12 +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 javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
@ -47,10 +51,9 @@ public class FileDao {
* @param limit Limit
* @return List of files
*/
@SuppressWarnings("unchecked")
public List<File> findAll(int offset, int limit) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select f from File f where f.deleteDate is null");
TypedQuery<File> q = em.createQuery("select f from File f where f.deleteDate is null", File.class);
q.setFirstResult(offset);
q.setMaxResults(limit);
return q.getResultList();
@ -62,28 +65,38 @@ public class FileDao {
* @param userId User ID
* @return List of files
*/
@SuppressWarnings("unchecked")
public List<File> findByUserId(String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select f from File f where f.userId = :userId and f.deleteDate is null");
TypedQuery<File> q = em.createQuery("select f from File f where f.userId = :userId and f.deleteDate is null", File.class);
q.setParameter("userId", userId);
return q.getResultList();
}
/**
* Returns a list of active files.
*
* @param ids Files IDs
* @return List of files
*/
public List<File> getFiles(List<String> ids) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
TypedQuery<File> q = em.createQuery("select f from File f where f.id in :ids and f.deleteDate is null", File.class);
q.setParameter("ids", ids);
return q.getResultList();
}
/**
* Returns an active file.
* Returns an active file or null.
*
* @param id File ID
* @return Document
* @return File
*/
public File getFile(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select f from File f where f.id = :id and f.deleteDate is null");
q.setParameter("id", id);
try {
return (File) q.getSingleResult();
} catch (NoResultException e) {
List<File> files = getFiles(List.of(id));
if (files.isEmpty()) {
return null;
} else {
return files.get(0);
}
}
@ -92,15 +105,15 @@ public class FileDao {
*
* @param id File ID
* @param userId User ID
* @return Document
* @return File
*/
public File getFile(String id, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select f from File f where f.id = :id and f.userId = :userId and f.deleteDate is null");
TypedQuery<File> q = em.createQuery("select f from File f where f.id = :id and f.userId = :userId and f.deleteDate is null", File.class);
q.setParameter("id", id);
q.setParameter("userId", userId);
try {
return (File) q.getSingleResult();
return q.getSingleResult();
} catch (NoResultException e) {
return null;
}
@ -116,9 +129,9 @@ public class FileDao {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the file
Query q = em.createQuery("select f from File f where f.id = :id and f.deleteDate is null");
TypedQuery<File> q = em.createQuery("select f from File f where f.id = :id and f.deleteDate is null", File.class);
q.setParameter("id", id);
File fileDb = (File) q.getSingleResult();
File fileDb = q.getSingleResult();
// Delete the file
Date dateNow = new Date();
@ -138,9 +151,9 @@ public class FileDao {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the file
Query q = em.createQuery("select f from File f where f.id = :id and f.deleteDate is null");
TypedQuery<File> q = em.createQuery("select f from File f where f.id = :id and f.deleteDate is null", File.class);
q.setParameter("id", file.getId());
File fileDb = (File) q.getSingleResult();
File fileDb = q.getSingleResult();
// Update the file
fileDb.setDocumentId(file.getDocumentId());
@ -150,10 +163,11 @@ public class FileDao {
fileDb.setMimeType(file.getMimeType());
fileDb.setVersionId(file.getVersionId());
fileDb.setLatestVersion(file.isLatestVersion());
fileDb.setSize(file.getSize());
return file;
}
/**
* Gets a file by its ID.
*
@ -162,46 +176,82 @@ public class FileDao {
*/
public File getActiveById(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select f from File f where f.id = :id and f.deleteDate is null");
TypedQuery<File> q = em.createQuery("select f from File f where f.id = :id and f.deleteDate is null", File.class);
q.setParameter("id", id);
try {
return (File) q.getSingleResult();
return q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Get files by document ID or all orphan files of an user.
* Get files by document ID or all orphan files of a user.
*
* @param userId User ID
* @param documentId Document ID
* @return List of files
*/
@SuppressWarnings("unchecked")
public List<File> getByDocumentId(String userId, String documentId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
if (documentId == null) {
Query q = em.createQuery("select f from File f where f.documentId is null and f.deleteDate is null and f.latestVersion = true and f.userId = :userId order by f.createDate asc");
TypedQuery<File> q = em.createQuery("select f from File f where f.documentId is null and f.deleteDate is null and f.latestVersion = true and f.userId = :userId order by f.createDate asc", File.class);
q.setParameter("userId", userId);
return q.getResultList();
} else {
return getByDocumentsIds(Collections.singleton(documentId));
}
Query q = em.createQuery("select f from File f where f.documentId = :documentId and f.latestVersion = true and f.deleteDate is null order by f.order asc");
q.setParameter("documentId", documentId);
}
/**
* Get files by documents IDs.
*
* @param documentIds Documents IDs
* @return List of files
*/
public List<File> getByDocumentsIds(Iterable<String> documentIds) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
TypedQuery<File> q = em.createQuery("select f from File f where f.documentId in :documentIds and f.latestVersion = true and f.deleteDate is null order by f.order asc", File.class);
q.setParameter("documentIds", documentIds);
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.
*
* @param versionId Version ID
* @return List of files
*/
@SuppressWarnings("unchecked")
public List<File> getByVersionId(String versionId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select f from File f where f.versionId = :versionId and f.deleteDate is null order by f.order asc");
TypedQuery<File> q = em.createQuery("select f from File f where f.versionId = :versionId and f.deleteDate is null order by f.order asc", File.class);
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();
}
}

View File

@ -12,9 +12,9 @@ import com.sismics.docs.core.util.jpa.QueryUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import jakarta.persistence.EntityManager;
import jakarta.persistence.NoResultException;
import jakarta.persistence.Query;
import java.util.*;
/**
@ -183,12 +183,10 @@ public class GroupDao {
}
criteriaList.add("g.GRP_DELETEDATE_D is null");
if (!criteriaList.isEmpty()) {
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
}
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
// Perform the search
QueryParam queryParam = QueryUtil.getSortedQueryParam(new QueryParam(sb.toString(), parameterMap), sortCriteria);
@SuppressWarnings("unchecked")

View File

@ -0,0 +1,146 @@
package com.sismics.docs.core.dao;
import com.google.common.base.Joiner;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.constant.MetadataType;
import com.sismics.docs.core.dao.criteria.MetadataCriteria;
import com.sismics.docs.core.dao.dto.MetadataDto;
import com.sismics.docs.core.model.jpa.Metadata;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.docs.core.util.jpa.QueryParam;
import com.sismics.docs.core.util.jpa.QueryUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
import jakarta.persistence.EntityManager;
import jakarta.persistence.NoResultException;
import jakarta.persistence.Query;
import java.util.*;
/**
* Metadata DAO.
*
* @author bgamard
*/
public class MetadataDao {
/**
* Creates a new metdata.
*
* @param metadata Metadata
* @param userId User ID
* @return New ID
*/
public String create(Metadata metadata, String userId) {
// Create the UUID
metadata.setId(UUID.randomUUID().toString());
// Create the metadata
EntityManager em = ThreadLocalContext.get().getEntityManager();
em.persist(metadata);
// Create audit log
AuditLogUtil.create(metadata, AuditLogType.CREATE, userId);
return metadata.getId();
}
/**
* Update a metadata.
*
* @param metadata Metadata to update
* @param userId User ID
* @return Updated metadata
*/
public Metadata update(Metadata metadata, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the metadata
Query q = em.createQuery("select r from Metadata r where r.id = :id and r.deleteDate is null");
q.setParameter("id", metadata.getId());
Metadata metadataDb = (Metadata) q.getSingleResult();
// Update the metadata
metadataDb.setName(metadata.getName());
// Create audit log
AuditLogUtil.create(metadataDb, AuditLogType.UPDATE, userId);
return metadataDb;
}
/**
* Gets an active metadata by its ID.
*
* @param id Metadata ID
* @return Metadata
*/
public Metadata getActiveById(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
try {
Query q = em.createQuery("select r from Metadata r where r.id = :id and r.deleteDate is null");
q.setParameter("id", id);
return (Metadata) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Deletes a metadata.
*
* @param id Metadata ID
* @param userId User ID
*/
public void delete(String id, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the metadata
Query q = em.createQuery("select r from Metadata r where r.id = :id and r.deleteDate is null");
q.setParameter("id", id);
Metadata metadataDb = (Metadata) q.getSingleResult();
// Delete the metadata
Date dateNow = new Date();
metadataDb.setDeleteDate(dateNow);
// Create audit log
AuditLogUtil.create(metadataDb, AuditLogType.DELETE, userId);
}
/**
* Returns the list of all metadata.
*
* @param criteria Search criteria
* @param sortCriteria Sort criteria
* @return List of metadata
*/
public List<MetadataDto> findByCriteria(MetadataCriteria criteria, SortCriteria sortCriteria) {
Map<String, Object> parameterMap = new HashMap<>();
List<String> criteriaList = new ArrayList<>();
StringBuilder sb = new StringBuilder("select m.MET_ID_C c0, m.MET_NAME_C c1, m.MET_TYPE_C c2");
sb.append(" from T_METADATA m ");
criteriaList.add("m.MET_DELETEDATE_D is null");
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
// Perform the search
QueryParam queryParam = QueryUtil.getSortedQueryParam(new QueryParam(sb.toString(), parameterMap), sortCriteria);
@SuppressWarnings("unchecked")
List<Object[]> l = QueryUtil.getNativeQuery(queryParam).getResultList();
// Assemble results
List<MetadataDto> dtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
MetadataDto dto = new MetadataDto();
dto.setId((String) o[i++]);
dto.setName((String) o[i++]);
dto.setType(MetadataType.valueOf((String) o[i]));
dtoList.add(dto);
}
return dtoList;
}
}

View File

@ -6,9 +6,9 @@ import com.sismics.util.context.ThreadLocalContext;
import org.joda.time.DateTime;
import org.joda.time.DurationFieldType;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import jakarta.persistence.EntityManager;
import jakarta.persistence.NoResultException;
import jakarta.persistence.Query;
import java.util.Date;
import java.util.UUID;

View File

@ -4,8 +4,8 @@ import com.sismics.docs.core.dao.dto.RelationDto;
import com.sismics.docs.core.model.jpa.Relation;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import java.util.*;
/**
@ -36,13 +36,13 @@ public class RelationDao {
List<Object[]> l = q.getResultList();
// Assemble results
List<RelationDto> relationDtoList = new ArrayList<RelationDto>();
List<RelationDto> relationDtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
RelationDto relationDto = new RelationDto();
relationDto.setId((String) o[i++]);
relationDto.setTitle((String) o[i++]);
String fromDocId = (String) o[i++];
String fromDocId = (String) o[i];
relationDto.setSource(documentId.equals(fromDocId));
relationDtoList.add(relationDto);
}

View File

@ -3,8 +3,8 @@ package com.sismics.docs.core.dao;
import com.google.common.collect.Sets;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import java.util.Set;
/**

View File

@ -11,7 +11,7 @@ import com.sismics.docs.core.util.jpa.QueryUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import jakarta.persistence.EntityManager;
import java.sql.Timestamp;
import java.util.*;
@ -64,10 +64,8 @@ public class RouteDao {
}
criteriaList.add("r.RTE_DELETEDATE_D is null");
if (!criteriaList.isEmpty()) {
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
}
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
// Perform the search
QueryParam queryParam = QueryUtil.getSortedQueryParam(new QueryParam(sb.toString(), parameterMap), sortCriteria);

View File

@ -4,7 +4,6 @@ import com.google.common.base.Joiner;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.dao.criteria.RouteModelCriteria;
import com.sismics.docs.core.dao.dto.RouteModelDto;
import com.sismics.docs.core.model.jpa.File;
import com.sismics.docs.core.model.jpa.RouteModel;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.docs.core.util.SecurityUtil;
@ -13,9 +12,9 @@ import com.sismics.docs.core.util.jpa.QueryUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import jakarta.persistence.EntityManager;
import jakarta.persistence.NoResultException;
import jakarta.persistence.Query;
import java.sql.Timestamp;
import java.util.*;
@ -62,7 +61,7 @@ public class RouteModelDao {
q.setParameter("id", routeModel.getId());
RouteModel routeModelDb = (RouteModel) q.getSingleResult();
// Update the group
// Update the route model
routeModelDb.setName(routeModel.getName());
routeModelDb.setSteps(routeModel.getSteps());
@ -146,10 +145,8 @@ public class RouteModelDao {
criteriaList.add("rm.RTM_DELETEDATE_D is null");
if (!criteriaList.isEmpty()) {
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
}
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
// Perform the search
QueryParam queryParam = QueryUtil.getSortedQueryParam(new QueryParam(sb.toString(), parameterMap), sortCriteria);

View File

@ -12,8 +12,8 @@ import com.sismics.docs.core.util.jpa.QueryUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import java.sql.Timestamp;
import java.util.*;
@ -90,10 +90,8 @@ public class RouteStepDao {
}
criteriaList.add("rs.RTP_DELETEDATE_D is null");
if (!criteriaList.isEmpty()) {
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
}
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
// Perform the search
QueryParam queryParam = QueryUtil.getSortedQueryParam(new QueryParam(sb.toString(), parameterMap), sortCriteria);

View File

@ -3,8 +3,8 @@ package com.sismics.docs.core.dao;
import com.sismics.docs.core.model.jpa.Share;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import java.util.Date;
import java.util.UUID;
@ -19,7 +19,6 @@ public class ShareDao {
*
* @param share Share
* @return New ID
* @throws Exception
*/
public String create(Share share) {
// Create the UUID

View File

@ -13,9 +13,9 @@ import com.sismics.docs.core.util.jpa.QueryUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import jakarta.persistence.EntityManager;
import jakarta.persistence.NoResultException;
import jakarta.persistence.Query;
import java.util.*;
/**
@ -199,10 +199,8 @@ public class TagDao {
criteriaList.add("t.TAG_DELETEDATE_D is null");
if (!criteriaList.isEmpty()) {
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
}
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
// Perform the search
QueryParam queryParam = QueryUtil.getSortedQueryParam(new QueryParam(sb.toString(), parameterMap), sortCriteria);

View File

@ -1,7 +1,14 @@
package com.sismics.docs.core.dao;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import at.favre.lib.crypto.bcrypt.BCrypt;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.dao.criteria.UserCriteria;
import com.sismics.docs.core.dao.dto.UserDto;
import com.sismics.docs.core.model.jpa.User;
@ -11,12 +18,10 @@ import com.sismics.docs.core.util.jpa.QueryParam;
import com.sismics.docs.core.util.jpa.QueryUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
import org.joda.time.DateTime;
import org.mindrot.jbcrypt.BCrypt;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import jakarta.persistence.EntityManager;
import jakarta.persistence.NoResultException;
import jakarta.persistence.Query;
import java.sql.Timestamp;
import java.util.*;
@ -26,6 +31,11 @@ import java.util.*;
* @author jtremeaux
*/
public class UserDao {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(UserDao.class);
/**
* Authenticates an user.
*
@ -39,7 +49,8 @@ public class UserDao {
q.setParameter("username", username);
try {
User user = (User) q.getSingleResult();
if (!BCrypt.checkpw(password, user.getPassword()) || user.getDisableDate() != null) {
BCrypt.Result result = BCrypt.verifyer().verify(password.toCharArray(), user.getPassword());
if (!result.verified || user.getDisableDate() != null) {
return null;
}
return user;
@ -277,7 +288,21 @@ public class UserDao {
* @return Hashed password
*/
private String hashPassword(String password) {
return BCrypt.hashpw(password, BCrypt.gensalt());
int bcryptWork = Constants.DEFAULT_BCRYPT_WORK;
String envBcryptWork = System.getenv(Constants.BCRYPT_WORK_ENV);
if (!Strings.isNullOrEmpty(envBcryptWork)) {
try {
int envBcryptWorkInt = Integer.parseInt(envBcryptWork);
if (envBcryptWorkInt >= 4 && envBcryptWorkInt <= 31) {
bcryptWork = envBcryptWorkInt;
} else {
log.warn(Constants.BCRYPT_WORK_ENV + " needs to be in range 4...31. Falling back to " + Constants.DEFAULT_BCRYPT_WORK + ".");
}
} catch (NumberFormatException e) {
log.warn(Constants.BCRYPT_WORK_ENV + " needs to be a number in range 4...31. Falling back to " + Constants.DEFAULT_BCRYPT_WORK + ".");
}
}
return BCrypt.withDefaults().hashToString(bcryptWork, password.toCharArray());
}
/**

View File

@ -3,9 +3,9 @@ package com.sismics.docs.core.dao;
import com.sismics.docs.core.model.jpa.Vocabulary;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import jakarta.persistence.EntityManager;
import jakarta.persistence.NoResultException;
import jakarta.persistence.Query;
import java.util.List;
import java.util.UUID;
@ -20,7 +20,6 @@ public class VocabularyDao {
*
* @param vocabulary Vocabulary
* @return New ID
* @throws Exception
*/
public String create(Vocabulary vocabulary) {
// Create the UUID

View File

@ -9,9 +9,9 @@ import com.sismics.docs.core.util.jpa.QueryUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import jakarta.persistence.EntityManager;
import jakarta.persistence.NoResultException;
import jakarta.persistence.Query;
import java.sql.Timestamp;
import java.util.*;
@ -42,11 +42,9 @@ public class WebhookDao {
}
criteriaList.add("w.WHK_DELETEDATE_D is null");
if (!criteriaList.isEmpty()) {
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
}
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
// Perform the search
QueryParam queryParam = QueryUtil.getSortedQueryParam(new QueryParam(sb.toString(), parameterMap), sortCriteria);
@SuppressWarnings("unchecked")

View File

@ -1,5 +1,6 @@
package com.sismics.docs.core.dao.criteria;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@ -49,13 +50,13 @@ public class DocumentCriteria {
* Tag IDs.
* The first level list will be AND'ed and the second level list will be OR'ed.
*/
private List<List<String>> tagIdList;
private List<List<String>> tagIdList = new ArrayList<>();
/**
* Tag IDs to excluded.
* Tag IDs to exclude.
* The first and second level list will be excluded.
*/
private List<List<String>> excludedTagIdList;
private List<List<String>> excludedTagIdList = new ArrayList<>();
/**
* Shared status.
@ -76,7 +77,17 @@ public class DocumentCriteria {
* A route is active.
*/
private Boolean activeRoute;
/**
* MIME type of a file.
*/
private String mimeType;
/**
* Titles to include.
*/
private List<String> titleList = new ArrayList<>();
public List<String> getTargetIdList() {
return targetIdList;
}
@ -121,19 +132,10 @@ public class DocumentCriteria {
return tagIdList;
}
public void setTagIdList(List<List<String>> tagIdList) {
this.tagIdList = tagIdList;
}
public List<List<String>> getExcludedTagIdList() {
return excludedTagIdList;
}
public DocumentCriteria setExcludedTagIdList(List<List<String>> excludedTagIdList) {
this.excludedTagIdList = excludedTagIdList;
return this;
}
public Boolean getShared() {
return shared;
}
@ -157,11 +159,7 @@ public class DocumentCriteria {
public void setCreatorId(String creatorId) {
this.creatorId = creatorId;
}
public Boolean getActiveRoute() {
return activeRoute;
}
public Date getUpdateDateMin() {
return updateDateMin;
}
@ -178,7 +176,23 @@ public class DocumentCriteria {
this.updateDateMax = updateDateMax;
}
public Boolean getActiveRoute() {
return activeRoute;
}
public void setActiveRoute(Boolean activeRoute) {
this.activeRoute = activeRoute;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public List<String> getTitleList() {
return titleList;
}
}

View File

@ -0,0 +1,9 @@
package com.sismics.docs.core.dao.criteria;
/**
* Metadata criteria.
*
* @author bgamard
*/
public class MetadataCriteria {
}

View File

@ -0,0 +1,94 @@
package com.sismics.docs.core.dao.dto;
import com.sismics.docs.core.constant.MetadataType;
/**
* Document metadata DTO.
*
* @author bgamard
*/
public class DocumentMetadataDto {
/**
* Document metadata ID.
*/
private String id;
/**
* Document ID.
*/
private String documentId;
/**
* Metadata ID.
*/
private String metadataId;
/**
* Name.
*/
private String name;
/**
* Value.
*/
private String value;
/**
* Type.
*/
private MetadataType type;
public String getId() {
return id;
}
public DocumentMetadataDto setId(String id) {
this.id = id;
return this;
}
public String getName() {
return name;
}
public DocumentMetadataDto setName(String name) {
this.name = name;
return this;
}
public MetadataType getType() {
return type;
}
public DocumentMetadataDto setType(MetadataType type) {
this.type = type;
return this;
}
public String getDocumentId() {
return documentId;
}
public DocumentMetadataDto setDocumentId(String documentId) {
this.documentId = documentId;
return this;
}
public String getMetadataId() {
return metadataId;
}
public DocumentMetadataDto setMetadataId(String metadataId) {
this.metadataId = metadataId;
return this;
}
public String getValue() {
return value;
}
public DocumentMetadataDto setValue(String value) {
this.value = value;
return this;
}
}

View File

@ -0,0 +1,52 @@
package com.sismics.docs.core.dao.dto;
import com.sismics.docs.core.constant.MetadataType;
/**
* Metadata DTO.
*
* @author bgamard
*/
public class MetadataDto {
/**
* Metadata ID.
*/
private String id;
/**
* Name.
*/
private String name;
/**
* Type.
*/
private MetadataType type;
public String getId() {
return id;
}
public MetadataDto setId(String id) {
this.id = id;
return this;
}
public String getName() {
return name;
}
public MetadataDto setName(String name) {
this.name = name;
return this;
}
public MetadataType getType() {
return type;
}
public MetadataDto setType(MetadataType type) {
this.type = type;
return this;
}
}

View File

@ -3,8 +3,8 @@ package com.sismics.docs.core.dao.dto;
import com.sismics.docs.core.constant.RouteStepType;
import com.sismics.util.JsonUtil;
import javax.json.Json;
import javax.json.JsonObjectBuilder;
import jakarta.json.Json;
import jakarta.json.JsonObjectBuilder;
/**
* Route step DTO.

View File

@ -1,7 +1,6 @@
package com.sismics.docs.core.event;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.model.jpa.Document;
/**
* Document created event.
@ -10,32 +9,22 @@ import com.sismics.docs.core.model.jpa.Document;
*/
public class DocumentCreatedAsyncEvent extends UserEvent {
/**
* Created document.
* Document ID.
*/
private Document document;
/**
* Getter of document.
*
* @return the document
*/
public Document getDocument() {
return document;
private String documentId;
public String getDocumentId() {
return documentId;
}
/**
* Setter of document.
*
* @param document document
*/
public void setDocument(Document document) {
this.document = document;
public void setDocumentId(String documentId) {
this.documentId = documentId;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("document", document)
.toString();
.add("documentId", documentId)
.toString();
}
}

View File

@ -1,7 +1,6 @@
package com.sismics.docs.core.event;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.model.jpa.File;
/**
* File deleted event.
@ -10,22 +9,33 @@ import com.sismics.docs.core.model.jpa.File;
*/
public class FileDeletedAsyncEvent extends UserEvent {
/**
* Deleted file.
* File ID.
*/
private File file;
public File getFile() {
return file;
private String fileId;
private Long fileSize;
public String getFileId() {
return fileId;
}
public void setFile(File file) {
this.file = file;
public void setFileId(String fileId) {
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("file", file)
.add("fileId", fileId)
.add("fileSize", fileSize)
.toString();
}
}
}

View File

@ -1,7 +1,6 @@
package com.sismics.docs.core.event;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.model.jpa.File;
import java.nio.file.Path;
@ -12,9 +11,9 @@ import java.nio.file.Path;
*/
public abstract class FileEvent extends UserEvent {
/**
* Created file.
* File ID.
*/
private File file;
private String fileId;
/**
* Language of the file.
@ -25,15 +24,15 @@ public abstract class FileEvent extends UserEvent {
* Unencrypted original file.
*/
private Path unencryptedFile;
public File getFile() {
return file;
public String getFileId() {
return fileId;
}
public void setFile(File file) {
this.file = file;
public void setFileId(String fileId) {
this.fileId = fileId;
}
public String getLanguage() {
return language;
}
@ -54,7 +53,7 @@ public abstract class FileEvent extends UserEvent {
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("file", file)
.add("fileId", fileId)
.add("language", language)
.toString();
}

View File

@ -3,9 +3,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.ContributorDao;
import com.sismics.docs.core.dao.DocumentDao;
import com.sismics.docs.core.event.DocumentCreatedAsyncEvent;
import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.model.jpa.Contributor;
import com.sismics.docs.core.model.jpa.Document;
import com.sismics.docs.core.util.TransactionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -34,15 +36,22 @@ public class DocumentCreatedAsyncListener {
}
TransactionUtil.handle(() -> {
// Fetch a fresh document
Document document = new DocumentDao().getById(event.getDocumentId());
if (document == null) {
// The document has been deleted since
return;
}
// Add the first contributor (the creator of the document)
ContributorDao contributorDao = new ContributorDao();
Contributor contributor = new Contributor();
contributor.setDocumentId(event.getDocument().getId());
contributor.setDocumentId(event.getDocumentId());
contributor.setUserId(event.getUserId());
contributorDao.create(contributor);
// Update index
AppContext.getInstance().getIndexingHandler().createDocument(event.getDocument());
AppContext.getInstance().getIndexingHandler().createDocument(document);
});
}
}

View File

@ -2,9 +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;
@ -12,7 +14,7 @@ import org.slf4j.LoggerFactory;
/**
* Listener on file deleted.
*
*
* @author bgamard
*/
public class FileDeletedAsyncListener {
@ -23,7 +25,7 @@ public class FileDeletedAsyncListener {
/**
* File deleted.
*
*
* @param event File deleted event
* @throws Exception e
*/
@ -33,14 +35,31 @@ 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
File file = event.getFile();
FileUtil.delete(file);
FileUtil.delete(event.getFileId());
TransactionUtil.handle(() -> {
// Update index
AppContext.getInstance().getIndexingHandler().deleteDocument(file.getId());
AppContext.getInstance().getIndexingHandler().deleteDocument(event.getFileId());
});
}
}

View File

@ -28,6 +28,7 @@ import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.concurrent.atomic.AtomicReference;
/**
* Listener on file processing.
@ -52,15 +53,7 @@ public class FileProcessingAsyncListener {
log.info("File created event: " + event.toString());
}
TransactionUtil.handle(() -> {
// Generate thumbnail, extract content
processFile(event);
// Update index
AppContext.getInstance().getIndexingHandler().createFile(event.getFile());
});
FileUtil.endProcessingFile(event.getFile().getId());
processFile(event, true);
}
/**
@ -71,43 +64,84 @@ public class FileProcessingAsyncListener {
@Subscribe
@AllowConcurrentEvents
public void on(final FileUpdatedAsyncEvent event) {
if (log.isInfoEnabled()) {
log.info("File updated event: " + event.toString());
}
log.info("File updated event: " + event.toString());
TransactionUtil.handle(() -> {
// Generate thumbnail, extract content
processFile(event);
// Update index
AppContext.getInstance().getIndexingHandler().updateFile(event.getFile());
});
FileUtil.endProcessingFile(event.getFile().getId());
processFile(event, false);
}
/**
* Process the file (create/update).
* Process a file :
* Generate thumbnails
* Extract and save text content
*
* @param event File event
* @param isFileCreated True if the file was just created
*/
private void processFile(FileEvent event) {
private void processFile(FileEvent event, boolean isFileCreated) {
AtomicReference<File> file = new AtomicReference<>();
AtomicReference<User> user = new AtomicReference<>();
// Open a first transaction to get what we need to start the processing
TransactionUtil.handle(() -> {
// Generate thumbnail, extract content
file.set(new FileDao().getActiveById(event.getFileId()));
if (file.get() == null) {
// The file has been deleted since
return;
}
// Get the creating user from the database for its private key
UserDao userDao = new UserDao();
user.set(userDao.getById(file.get().getUserId()));
});
// Process the file outside of a transaction
if (user.get() == null || file.get() == null) {
// The user or file has been deleted
FileUtil.endProcessingFile(event.getFileId());
return;
}
String content = extractContent(event, user.get(), file.get());
// Open a new transaction to save the file content
TransactionUtil.handle(() -> {
// Save the file to database
FileDao fileDao = new FileDao();
File freshFile = fileDao.getActiveById(event.getFileId());
if (freshFile == null) {
// The file has been deleted since the text extraction started, ignore the result
return;
}
freshFile.setContent(content);
fileDao.update(freshFile);
// Update index with the updated file
if (isFileCreated) {
AppContext.getInstance().getIndexingHandler().createFile(freshFile);
} else {
AppContext.getInstance().getIndexingHandler().updateFile(freshFile);
}
});
FileUtil.endProcessingFile(event.getFileId());
}
/**
* Extract text content from a file.
* This is executed outside of a transaction.
*
* @param event File event
* @param user User whom created the file
* @param file Fresh file
* @return Text content
*/
private String extractContent(FileEvent event, User user, File file) {
// Find a format handler
final File file = event.getFile();
FormatHandler formatHandler = FormatHandlerUtil.find(file.getMimeType());
if (formatHandler == null) {
log.info("Format unhandled: " + file.getMimeType());
FileUtil.endProcessingFile(file.getId());
return;
}
// Get the user from the database
UserDao userDao = new UserDao();
User user = userDao.getById(event.getUserId());
if (user == null) {
// The user has been deleted meanwhile
FileUtil.endProcessingFile(file.getId());
return;
return null;
}
// Generate file variations
@ -132,28 +166,21 @@ public class FileProcessingAsyncListener {
ImageUtil.writeJpeg(thumbnail, outputStream);
}
}
} catch (Exception e) {
log.error("Unable to generate thumbnails", e);
} catch (Throwable e) {
log.error("Unable to generate thumbnails for: " + file, e);
}
// Extract text content from the file
long startTime = System.currentTimeMillis();
String content = null;
log.info("Start extracting content from: " + file);
try {
content = formatHandler.extractContent(event.getLanguage(), event.getUnencryptedFile());
} catch (Exception e) {
log.error("Error extracting content from: " + event.getFile(), e);
} catch (Throwable e) {
log.error("Error extracting content from: " + file, e);
}
log.info(MessageFormat.format("File content extracted in {0}ms", System.currentTimeMillis() - startTime));
log.info(MessageFormat.format("File content extracted in {0}ms: " + file.getId(), System.currentTimeMillis() - startTime));
// Save the file to database
FileDao fileDao = new FileDao();
if (fileDao.getActiveById(file.getId()) == null) {
// The file has been deleted since the text extraction started, ignore the result
return;
}
file.setContent(content);
fileDao.update(file);
return content;
}
}

View File

@ -36,7 +36,7 @@ public class WebhookAsyncListener {
@Subscribe
@AllowConcurrentEvents
public void on(final DocumentCreatedAsyncEvent event) {
triggerWebhook(WebhookEvent.DOCUMENT_CREATED, event.getDocument().getId());
triggerWebhook(WebhookEvent.DOCUMENT_CREATED, event.getDocumentId());
}
@Subscribe
@ -54,19 +54,19 @@ public class WebhookAsyncListener {
@Subscribe
@AllowConcurrentEvents
public void on(final FileCreatedAsyncEvent event) {
triggerWebhook(WebhookEvent.FILE_CREATED, event.getFile().getId());
triggerWebhook(WebhookEvent.FILE_CREATED, event.getFileId());
}
@Subscribe
@AllowConcurrentEvents
public void on(final FileUpdatedAsyncEvent event) {
triggerWebhook(WebhookEvent.FILE_UPDATED, event.getFile().getId());
triggerWebhook(WebhookEvent.FILE_UPDATED, event.getFileId());
}
@Subscribe
@AllowConcurrentEvents
public void on(final FileDeletedAsyncEvent event) {
triggerWebhook(WebhookEvent.FILE_DELETED, event.getFile().getId());
triggerWebhook(WebhookEvent.FILE_DELETED, event.getFileId());
}
/**
@ -86,7 +86,7 @@ public class WebhookAsyncListener {
}
});
RequestBody body = RequestBody.create(JSON, "{\"event\": \"" + event.name() + "\", \"id\": \"" + id + "\"}");
RequestBody body = RequestBody.create("{\"event\": \"" + event.name() + "\", \"id\": \"" + id + "\"}", JSON);
for (String webhookUrl : webhookUrlList) {
Request request = new Request.Builder()

View File

@ -1,14 +1,15 @@
package com.sismics.docs.core.model.context;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.dao.UserDao;
import com.sismics.docs.core.event.RebuildIndexAsyncEvent;
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.
*/
@ -81,7 +87,7 @@ public class AppContext {
List<Class<? extends IndexingHandler>> indexingHandlerList = Lists.newArrayList(
new ClasspathScanner<IndexingHandler>().findClasses(IndexingHandler.class, "com.sismics.docs.core.util.indexing"));
for (Class<? extends IndexingHandler> handlerClass : indexingHandlerList) {
IndexingHandler handler = handlerClass.newInstance();
IndexingHandler handler = handlerClass.getDeclaredConstructor().newInstance();
if (handler.accept()) {
indexingHandler = handler;
break;
@ -102,12 +108,17 @@ public class AppContext {
inboxService.startAsync();
inboxService.awaitRunning();
// Start file size service
fileSizeService = new FileSizeService();
fileSizeService.startAsync();
fileSizeService.awaitRunning();
// Register fonts
PdfUtil.registerFonts();
// Change the admin password if needed
String envAdminPassword = System.getenv(Constants.ADMIN_PASSWORD_INIT_ENV);
if (envAdminPassword != null) {
if (!Strings.isNullOrEmpty(envAdminPassword)) {
UserDao userDao = new UserDao();
User adminUser = userDao.getById("admin");
if (Constants.DEFAULT_ADMIN_PASSWORD.equals(adminUser.getPassword())) {
@ -118,7 +129,7 @@ public class AppContext {
// Change the admin email if needed
String envAdminEmail = System.getenv(Constants.ADMIN_EMAIL_INIT_ENV);
if (envAdminEmail != null) {
if (!Strings.isNullOrEmpty(envAdminEmail)) {
UserDao userDao = new UserDao();
User adminUser = userDao.getById("admin");
if (Constants.DEFAULT_ADMIN_EMAIL.equals(adminUser.getEmail())) {
@ -172,7 +183,8 @@ public class AppContext {
if (EnvironmentUtil.isUnitTest()) {
return new EventBus();
} else {
ThreadPoolExecutor executor = new ThreadPoolExecutor(8, 8,
int threadCount = Math.max(Runtime.getRuntime().availableProcessors() / 2, 2);
ThreadPoolExecutor executor = new ThreadPoolExecutor(threadCount, threadCount,
1L, TimeUnit.MINUTES,
new LinkedBlockingQueue<>());
asyncExecutorList.add(executor);
@ -237,6 +249,10 @@ public class AppContext {
fileService.stopAsync();
}
if (fileSizeService != null) {
fileSizeService.stopAsync();
}
instance = null;
}
}

View File

@ -4,7 +4,7 @@ import com.google.common.base.MoreObjects;
import com.sismics.docs.core.constant.AclType;
import com.sismics.docs.core.constant.PermType;
import javax.persistence.*;
import jakarta.persistence.*;
import java.util.Date;
/**

View File

@ -2,12 +2,12 @@ package com.sismics.docs.core.model.jpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.constant.AuditLogType;

View File

@ -2,10 +2,10 @@ package com.sismics.docs.core.model.jpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import com.google.common.base.MoreObjects;

View File

@ -1,9 +1,9 @@
package com.sismics.docs.core.model.jpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import com.google.common.base.MoreObjects;

View File

@ -2,10 +2,10 @@ package com.sismics.docs.core.model.jpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import com.google.common.base.MoreObjects;

View File

@ -1,11 +1,11 @@
package com.sismics.docs.core.model.jpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.constant.ConfigType;

View File

@ -1,9 +1,9 @@
package com.sismics.docs.core.model.jpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import com.google.common.base.MoreObjects;

View File

@ -2,10 +2,10 @@ package com.sismics.docs.core.model.jpa;
import com.google.common.base.MoreObjects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.Date;
/**

View File

@ -0,0 +1,91 @@
package com.sismics.docs.core.model.jpa;
import com.google.common.base.MoreObjects;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.io.Serializable;
/**
* Link between a document and a metadata, holding the value.
*
* @author bgamard
*/
@Entity
@Table(name = "T_DOCUMENT_METADATA")
public class DocumentMetadata implements Serializable {
/**
* Serial version UID.
*/
private static final long serialVersionUID = 1L;
/**
* Document metadata ID.
*/
@Id
@Column(name = "DME_ID_C", length = 36)
private String id;
/**
* Document ID.
*/
@Column(name = "DME_IDDOCUMENT_C", nullable = false, length = 36)
private String documentId;
/**
* Metadata ID.
*/
@Column(name = "DME_IDMETADATA_C", nullable = false, length = 36)
private String metadataId;
/**
* Value.
*/
@Column(name = "DME_VALUE_C", length = 4000)
private String value;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getDocumentId() {
return documentId;
}
public void setDocumentId(String documentId) {
this.documentId = documentId;
}
public String getMetadataId() {
return metadataId;
}
public DocumentMetadata setMetadataId(String metadataId) {
this.metadataId = metadataId;
return this;
}
public String getValue() {
return value;
}
public DocumentMetadata setValue(String value) {
this.value = value;
return this;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("id", id)
.add("documentId", documentId)
.add("metadataId", metadataId)
.toString();
}
}

View File

@ -3,10 +3,10 @@ package com.sismics.docs.core.model.jpa;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import com.google.common.base.MoreObjects;

View File

@ -4,7 +4,7 @@ import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.sismics.util.mime.MimeTypeUtil;
import javax.persistence.*;
import jakarta.persistence.*;
import java.util.Date;
/**
@ -49,7 +49,6 @@ public class File implements Loggable {
/**
* OCR-ized content.
*/
@Lob
@Column(name = "FIL_CONTENT_C")
private String content;
@ -89,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.
@ -205,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)

View File

@ -2,10 +2,10 @@ package com.sismics.docs.core.model.jpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import com.google.common.base.MoreObjects;

View File

@ -0,0 +1,92 @@
package com.sismics.docs.core.model.jpa;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.constant.MetadataType;
import jakarta.persistence.*;
import java.util.Date;
/**
* Metadata entity.
*
* @author bgamard
*/
@Entity
@Table(name = "T_METADATA")
public class Metadata implements Loggable {
/**
* Metadata ID.
*/
@Id
@Column(name = "MET_ID_C", length = 36)
private String id;
/**
* Name.
*/
@Column(name = "MET_NAME_C", length = 50, nullable = false)
private String name;
/**
* Type.
*/
@Column(name = "MET_TYPE_C", length = 20, nullable = false)
@Enumerated(EnumType.STRING)
private MetadataType type;
/**
* Deletion date.
*/
@Column(name = "MET_DELETEDATE_D")
private Date deleteDate;
public String getId() {
return id;
}
public Metadata setId(String id) {
this.id = id;
return this;
}
public String getName() {
return name;
}
public Metadata setName(String name) {
this.name = name;
return this;
}
public MetadataType getType() {
return type;
}
public Metadata setType(MetadataType type) {
this.type = type;
return this;
}
@Override
public Date getDeleteDate() {
return deleteDate;
}
public void setDeleteDate(Date deleteDate) {
this.deleteDate = deleteDate;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("id", id)
.add("name", name)
.add("type", type)
.toString();
}
@Override
public String toMessage() {
return name;
}
}

View File

@ -2,10 +2,10 @@ package com.sismics.docs.core.model.jpa;
import com.google.common.base.MoreObjects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.Date;
/**

View File

@ -2,10 +2,10 @@ package com.sismics.docs.core.model.jpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import com.google.common.base.MoreObjects;

View File

@ -2,10 +2,10 @@ package com.sismics.docs.core.model.jpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import com.google.common.base.MoreObjects;

View File

@ -2,10 +2,10 @@ package com.sismics.docs.core.model.jpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import com.google.common.base.MoreObjects;

View File

@ -2,10 +2,10 @@ package com.sismics.docs.core.model.jpa;
import com.google.common.base.MoreObjects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.Date;
/**

View File

@ -2,10 +2,10 @@ package com.sismics.docs.core.model.jpa;
import com.google.common.base.MoreObjects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.Date;
/**

View File

@ -4,7 +4,7 @@ import com.google.common.base.MoreObjects;
import com.sismics.docs.core.constant.RouteStepTransition;
import com.sismics.docs.core.constant.RouteStepType;
import javax.persistence.*;
import jakarta.persistence.*;
import java.util.Date;
/**

View File

@ -2,10 +2,10 @@ package com.sismics.docs.core.model.jpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import com.google.common.base.MoreObjects;

View File

@ -2,10 +2,10 @@ package com.sismics.docs.core.model.jpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import com.google.common.base.MoreObjects;

View File

@ -2,10 +2,10 @@ package com.sismics.docs.core.model.jpa;
import com.google.common.base.MoreObjects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.Date;
/**

View File

@ -3,10 +3,10 @@ package com.sismics.docs.core.model.jpa;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import com.google.common.base.MoreObjects;

View File

@ -1,9 +1,9 @@
package com.sismics.docs.core.model.jpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import com.google.common.base.MoreObjects;

View File

@ -3,7 +3,7 @@ package com.sismics.docs.core.model.jpa;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.constant.WebhookEvent;
import javax.persistence.*;
import jakarta.persistence.*;
import java.util.Date;
/**

View File

@ -69,13 +69,18 @@ public class FileService extends AbstractScheduledService {
return Scheduler.newFixedDelaySchedule(0, 5, TimeUnit.SECONDS);
}
public Path createTemporaryFile() throws IOException {
return createTemporaryFile(null);
}
/**
* Create a temporary file.
*
* @param name Wanted file name
* @return New temporary file
*/
public Path createTemporaryFile() throws IOException {
Path path = Files.createTempFile("sismics_docs", null);
public Path createTemporaryFile(String name) throws IOException {
Path path = Files.createTempFile("sismics_docs", name);
referenceSet.add(new TemporaryPathReference(path, referenceQueue));
return path;
}
@ -85,7 +90,7 @@ public class FileService extends AbstractScheduledService {
*
* @author bgamard
*/
class TemporaryPathReference extends PhantomReference<Path> {
static class TemporaryPathReference extends PhantomReference<Path> {
String path;
TemporaryPathReference(Path referent, ReferenceQueue<? super Path> q) {
super(referent, q);

View File

@ -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);
}
}

View File

@ -1,9 +1,10 @@
package com.sismics.docs.core.service;
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.Document;
import com.sismics.docs.core.model.jpa.Tag;
@ -11,17 +12,19 @@ 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.TransactionUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.EmailUtil;
import com.sismics.util.context.ThreadLocalContext;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.mail.*;
import javax.mail.search.FlagTerm;
import java.util.Date;
import java.util.Properties;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Inbox scanning service.
@ -79,22 +82,25 @@ public class InboxService extends AbstractScheduledService {
lastSyncDate = new Date();
lastSyncMessageCount = 0;
try {
Map<String, String> tagsNameToId = getAllTags();
inbox = openInbox();
Message[] messages = inbox.search(new FlagTerm(new Flags(Flags.Flag.SEEN), false));
log.info(messages.length + " messages found");
for (Message message : messages) {
importMessage(message);
importMessage(message, tagsNameToId);
lastSyncMessageCount++;
}
} catch (FolderClosedException e) {
// Ignore this, we will just continue importing on the next cycle
} catch (Exception e) {
log.error("Error synching the inbox", e);
log.error("Error syncing the inbox", e);
lastSyncError = e.getMessage();
} finally {
try {
if (inbox != null) {
inbox.close(false);
// The parameter controls if the messages flagged to be deleted, should actually get deleted.
inbox.close(ConfigUtil.getConfigBooleanValue(ConfigType.INBOX_DELETE_IMPORTED));
inbox.getStore().close();
}
} catch (Exception e) {
@ -150,6 +156,7 @@ public class InboxService extends AbstractScheduledService {
String port = ConfigUtil.getConfigStringValue(ConfigType.INBOX_PORT);
properties.put("mail.imap.host", ConfigUtil.getConfigStringValue(ConfigType.INBOX_HOSTNAME));
properties.put("mail.imap.port", port);
properties.setProperty("mail.imap.starttls.enable", ConfigUtil.getConfigStringValue(ConfigType.INBOX_STARTTLS).toString());
boolean isSsl = "993".equals(port);
properties.put("mail.imap.ssl.enable", String.valueOf(isSsl));
properties.setProperty("mail.imap.socketFactory.class",
@ -172,7 +179,7 @@ public class InboxService extends AbstractScheduledService {
store.connect(ConfigUtil.getConfigStringValue(ConfigType.INBOX_USERNAME),
ConfigUtil.getConfigStringValue(ConfigType.INBOX_PASSWORD));
Folder inbox = store.getFolder("INBOX");
Folder inbox = store.getFolder(ConfigUtil.getConfigStringValue(ConfigType.INBOX_FOLDER));
inbox.open(Folder.READ_WRITE);
return inbox;
}
@ -183,7 +190,7 @@ public class InboxService extends AbstractScheduledService {
* @param message Message
* @throws Exception e
*/
private void importMessage(Message message) throws Exception {
private void importMessage(Message message, Map<String, String> tags) throws Exception {
log.info("Importing message: " + message.getSubject());
// Parse the mail
@ -194,12 +201,27 @@ public class InboxService extends AbstractScheduledService {
// Create the document
Document document = new Document();
document.setUserId("admin");
if (mailContent.getSubject() == null) {
document.setTitle("Imported email from EML file");
} else {
document.setTitle(StringUtils.abbreviate(mailContent.getSubject(), 100));
String subject = mailContent.getSubject();
if (subject == null) {
subject = "Imported email from EML file";
}
HashSet<String> tagsFound = new HashSet<>();
if (tags != null) {
Pattern pattern = Pattern.compile("#([^\\s:#]+)");
Matcher matcher = pattern.matcher(subject);
while (matcher.find()) {
if (tags.containsKey(matcher.group(1)) && tags.get(matcher.group(1)) != null) {
tagsFound.add(tags.get(matcher.group(1)));
subject = subject.replaceFirst("#" + matcher.group(1), "");
}
}
log.debug("Tags found: " + String.join(", ", tagsFound));
subject = subject.trim().replaceAll(" +", " ");
}
document.setUserId("admin");
document.setTitle(StringUtils.abbreviate(subject, 100));
document.setDescription(StringUtils.abbreviate(mailContent.getMessage(), 4000));
document.setSubject(StringUtils.abbreviate(mailContent.getSubject(), 500));
document.setFormat("EML");
@ -220,14 +242,19 @@ public class InboxService extends AbstractScheduledService {
TagDao tagDao = new TagDao();
Tag tag = tagDao.getById(tagId);
if (tag != null) {
tagDao.updateTagList(document.getId(), Sets.newHashSet(tagId));
tagsFound.add(tagId);
}
}
// Update tags
if (!tagsFound.isEmpty()) {
new TagDao().updateTagList(document.getId(), tagsFound);
}
// Raise a document created event
DocumentCreatedAsyncEvent documentCreatedAsyncEvent = new DocumentCreatedAsyncEvent();
documentCreatedAsyncEvent.setUserId("admin");
documentCreatedAsyncEvent.setDocument(document);
documentCreatedAsyncEvent.setDocumentId(document.getId());
ThreadLocalContext.get().addAsyncEvent(documentCreatedAsyncEvent);
// Add files to the document
@ -235,6 +262,29 @@ public class InboxService extends AbstractScheduledService {
FileUtil.createFile(fileContent.getName(), null, fileContent.getFile(), fileContent.getSize(),
document.getLanguage(), "admin", document.getId());
}
if (ConfigUtil.getConfigBooleanValue(ConfigType.INBOX_DELETE_IMPORTED)) {
message.setFlag(Flags.Flag.DELETED, true);
}
}
/**
* Fetches a HashMap with all tag names as keys and their respective ids as values.
*
* @return Map with all tags or null if not enabled
*/
private Map<String, String> getAllTags() {
if (!ConfigUtil.getConfigBooleanValue(ConfigType.INBOX_AUTOMATIC_TAGS)) {
return null;
}
TagDao tagDao = new TagDao();
List<TagDto> tags = tagDao.findByCriteria(new TagCriteria().setTargetIdList(null), new SortCriteria(1, true));
Map<String, String> tagsNameToId = new HashMap<>();
for (TagDto tagDto : tags) {
tagsNameToId.put(tagDto.getName(), tagDto.getId());
}
return tagsNameToId;
}
public Date getLastSyncDate() {

View File

@ -9,7 +9,7 @@ import com.sismics.docs.core.util.action.RemoveTagAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.json.JsonObject;
import jakarta.json.JsonObject;
/**
* Action utilities.

View File

@ -6,7 +6,7 @@ import com.sismics.docs.core.model.jpa.AuditLog;
import com.sismics.docs.core.model.jpa.Loggable;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import jakarta.persistence.EntityManager;
/**
* Audit log utilities.

View File

@ -50,6 +50,19 @@ public class ConfigUtil {
return Integer.parseInt(value);
}
/**
* Returns the long value of a configuration parameter.
*
* @param configType Type of the configuration parameter
* @return Long value of the configuration parameter
* @throws IllegalStateException Configuration parameter undefined
*/
public static long getConfigLongValue(ConfigType configType) {
String value = getConfigStringValue(configType);
return Long.parseLong(value);
}
/**
* Returns the boolean value of a configuration parameter.
*

View File

@ -5,7 +5,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.StringUtils;
import com.sismics.util.EnvironmentUtil;

View File

@ -1,6 +1,5 @@
package com.sismics.docs.core.util;
import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.io.CharStreams;
@ -17,7 +16,12 @@ 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.lang.StringUtils;
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;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
@ -26,6 +30,7 @@ import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
@ -36,10 +41,15 @@ import java.util.*;
* @author bgamard
*/
public class FileUtil {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(FileUtil.class);
/**
* 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.
@ -69,19 +79,19 @@ public class FileUtil {
// Consume the data as text
try (InputStream is = process.getInputStream()) {
return CharStreams.toString(new InputStreamReader(is, Charsets.UTF_8));
return CharStreams.toString(new InputStreamReader(is, StandardCharsets.UTF_8));
}
}
/**
* Remove a file from the storage filesystem.
*
* @param file File to delete
* @param fileId ID of file to delete
*/
public static void delete(File file) throws IOException {
Path storedFile = DirectoryUtil.getStorageDirectory().resolve(file.getId());
Path webFile = DirectoryUtil.getStorageDirectory().resolve(file.getId() + "_web");
Path thumbnailFile = DirectoryUtil.getStorageDirectory().resolve(file.getId() + "_thumb");
public static void delete(String fileId) throws IOException {
Path storedFile = DirectoryUtil.getStorageDirectory().resolve(fileId);
Path webFile = DirectoryUtil.getStorageDirectory().resolve(fileId + "_web");
Path thumbnailFile = DirectoryUtil.getStorageDirectory().resolve(fileId + "_thumb");
if (Files.exists(storedFile)) {
Files.delete(storedFile);
@ -126,7 +136,7 @@ public class FileUtil {
// Validate global quota
String globalStorageQuotaStr = System.getenv(Constants.GLOBAL_QUOTA_ENV);
if (!Strings.isNullOrEmpty(globalStorageQuotaStr)) {
long globalStorageQuota = Long.valueOf(globalStorageQuotaStr);
long globalStorageQuota = Long.parseLong(globalStorageQuotaStr);
long globalStorageCurrent = userDao.getGlobalStorageCurrent();
if (globalStorageCurrent + fileSize > globalStorageQuota) {
throw new IOException("QuotaReached");
@ -142,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();
@ -190,7 +201,7 @@ public class FileUtil {
FileCreatedAsyncEvent fileCreatedAsyncEvent = new FileCreatedAsyncEvent();
fileCreatedAsyncEvent.setUserId(userId);
fileCreatedAsyncEvent.setLanguage(language);
fileCreatedAsyncEvent.setFile(file);
fileCreatedAsyncEvent.setFileId(file.getId());
fileCreatedAsyncEvent.setUnencryptedFile(unencryptedFile);
ThreadLocalContext.get().addAsyncEvent(fileCreatedAsyncEvent);
@ -211,6 +222,7 @@ public class FileUtil {
*/
public static void startProcessingFile(String fileId) {
processingFileSet.add(fileId);
log.info("Processing started for file: " + fileId);
}
/**
@ -220,6 +232,7 @@ public class FileUtil {
*/
public static void endProcessingFile(String fileId) {
processingFileSet.remove(fileId);
log.info("Processing ended for file: " + fileId);
}
/**
@ -231,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;
}
}
}

View File

@ -0,0 +1,196 @@
package com.sismics.docs.core.util;
import com.google.common.collect.Maps;
import com.sismics.docs.core.constant.MetadataType;
import com.sismics.docs.core.dao.DocumentMetadataDao;
import com.sismics.docs.core.dao.MetadataDao;
import com.sismics.docs.core.dao.criteria.MetadataCriteria;
import com.sismics.docs.core.dao.dto.DocumentMetadataDto;
import com.sismics.docs.core.dao.dto.MetadataDto;
import com.sismics.docs.core.model.jpa.DocumentMetadata;
import com.sismics.docs.core.util.jpa.SortCriteria;
import jakarta.json.Json;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonObjectBuilder;
import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
/**
* Metadata utilities.
*
* @author bgamard
*/
public class MetadataUtil {
/**
* Update custom metadata on a document.
*
* @param documentId Document ID
* @param metadataIdList Metadata ID list
* @param metadataValueList Metadata value list
*/
public static void updateMetadata(String documentId, List<String> metadataIdList, List<String> metadataValueList) throws Exception {
if (metadataIdList == null || metadataValueList == null || metadataIdList.isEmpty()) {
return;
}
if (metadataIdList.size() != metadataValueList.size()) {
throw new Exception("metadata_id and metadata_value must have the same length");
}
Map<String, String> newValues = Maps.newHashMap();
for (int i = 0; i < metadataIdList.size(); i++) {
newValues.put(metadataIdList.get(i), metadataValueList.get(i));
}
MetadataDao metadataDao = new MetadataDao();
DocumentMetadataDao documentMetadataDao = new DocumentMetadataDao();
List<MetadataDto> metadataDtoList = metadataDao.findByCriteria(new MetadataCriteria(), null);
List<DocumentMetadataDto> documentMetadataDtoList = documentMetadataDao.getByDocumentId(documentId);
// Update existing values
for (DocumentMetadataDto documentMetadataDto : documentMetadataDtoList) {
if (newValues.containsKey(documentMetadataDto.getMetadataId())) {
// Update the value
String value = newValues.get(documentMetadataDto.getMetadataId());
validateValue(documentMetadataDto.getType(), value);
updateValue(documentMetadataDto.getId(), value);
newValues.remove(documentMetadataDto.getMetadataId());
} else {
// Remove the value
updateValue(documentMetadataDto.getId(), null);
}
}
// Create new values
for (Map.Entry<String, String> entry : newValues.entrySet()) {
// Search the metadata definition
MetadataDto metadata = null;
for (MetadataDto metadataDto : metadataDtoList) {
if (metadataDto.getId().equals(entry.getKey())) {
metadata = metadataDto;
break;
}
}
if (metadata == null) {
throw new Exception(MessageFormat.format("Metadata not found: {0}", entry.getKey()));
}
// Add the value
validateValue(metadata.getType(), entry.getValue());
createValue(documentId, entry.getKey(), entry.getValue());
}
}
/**
* Validate a custom metadata value.
*
* @param type Metadata type
* @param value Value
* @throws Exception In case of validation error
*/
private static void validateValue(MetadataType type, String value) throws Exception {
switch (type) {
case STRING:
case BOOLEAN:
return;
case DATE:
try {
Long.parseLong(value);
} catch (NumberFormatException e) {
throw new Exception("Date value not parsable as timestamp");
}
break;
case FLOAT:
try {
Double.parseDouble(value);
} catch (NumberFormatException e) {
throw new Exception("Float value not parsable");
}
break;
case INTEGER:
try {
Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new Exception("Integer value not parsable");
}
break;
}
}
/**
* Create a custom metadata value on a document.
*
* @param documentId Document ID
* @param metadataId Metadata ID
* @param value Value
*/
private static void createValue(String documentId, String metadataId, String value) {
DocumentMetadataDao documentMetadataDao = new DocumentMetadataDao();
DocumentMetadata documentMetadata = new DocumentMetadata();
documentMetadata.setDocumentId(documentId);
documentMetadata.setMetadataId(metadataId);
documentMetadata.setValue(value);
documentMetadataDao.create(documentMetadata);
}
/**
* Update a custom metadata value.
*
* @param documentMetadataId Document metadata ID
* @param value Value
*/
private static void updateValue(String documentMetadataId, String value) {
DocumentMetadataDao documentMetadataDao = new DocumentMetadataDao();
DocumentMetadata documentMetadata = new DocumentMetadata();
documentMetadata.setId(documentMetadataId);
documentMetadata.setValue(value);
documentMetadataDao.update(documentMetadata);
}
/**
* Add custom metadata to a JSON response.
*
* @param json JSON
* @param documentId Document ID
*/
public static void addMetadata(JsonObjectBuilder json, String documentId) {
DocumentMetadataDao documentMetadataDao = new DocumentMetadataDao();
MetadataDao metadataDao = new MetadataDao();
List<MetadataDto> metadataDtoList = metadataDao.findByCriteria(new MetadataCriteria(), new SortCriteria(1, true));
List<DocumentMetadataDto> documentMetadataDtoList = documentMetadataDao.getByDocumentId(documentId);
JsonArrayBuilder metadata = Json.createArrayBuilder();
for (MetadataDto metadataDto : metadataDtoList) {
JsonObjectBuilder meta = Json.createObjectBuilder()
.add("id", metadataDto.getId())
.add("name", metadataDto.getName())
.add("type", metadataDto.getType().name());
for (DocumentMetadataDto documentMetadataDto : documentMetadataDtoList) {
if (documentMetadataDto.getMetadataId().equals(metadataDto.getId())) {
if (documentMetadataDto.getValue() != null) {
switch (metadataDto.getType()) {
case STRING:
meta.add("value", documentMetadataDto.getValue());
break;
case BOOLEAN:
meta.add("value", Boolean.parseBoolean(documentMetadataDto.getValue()));
break;
case DATE:
meta.add("value", Long.parseLong(documentMetadataDto.getValue()));
break;
case FLOAT:
meta.add("value", Double.parseDouble(documentMetadataDto.getValue()));
break;
case INTEGER:
meta.add("value", Integer.parseInt(documentMetadataDto.getValue()));
break;
}
}
}
}
metadata.add(meta);
}
json.add("metadata", metadata);
}
}

View File

@ -19,10 +19,10 @@ import com.sismics.docs.core.model.jpa.Document;
import com.sismics.docs.core.model.jpa.RouteModel;
import com.sismics.util.context.ThreadLocalContext;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonReader;
import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.json.JsonReader;
import java.io.StringReader;
import java.util.List;

View File

@ -1,8 +1,8 @@
package com.sismics.docs.core.util;
import com.google.common.collect.Lists;
import com.sismics.docs.core.dao.dto.TagDto;
import java.util.ArrayList;
import java.util.List;
/**
@ -12,14 +12,14 @@ import java.util.List;
*/
public class TagUtil {
/**
* Recursively find children of a tags.
* Recursively find children of a tag.
*
* @param parentTagDto Parent tag
* @param allTagDtoList List of all tags
* @return Children tags
*/
public static List<TagDto> findChildren(TagDto parentTagDto, List<TagDto> allTagDtoList) {
List<TagDto> childrenTagDtoList = Lists.newArrayList();
List<TagDto> childrenTagDtoList = new ArrayList<>();
for (TagDto tagDto : allTagDtoList) {
if (parentTagDto.getId().equals(tagDto.getParentId())) {
@ -32,15 +32,15 @@ public class TagUtil {
}
/**
* Find tags by name (start with).
* Find tags by name (start with, ignore case).
*
* @param name Name
* @param allTagDtoList List of all tags
* @return List of filtered tags
*/
public static List<TagDto> findByName(String name, List<TagDto> allTagDtoList) {
List<TagDto> tagDtoList = Lists.newArrayList();
if (name == null || name.isEmpty()) {
List<TagDto> tagDtoList = new ArrayList<>();
if (name.isEmpty()) {
return tagDtoList;
}
name = name.toLowerCase();

View File

@ -5,8 +5,8 @@ import com.sismics.util.jpa.EMF;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityTransaction;
/**
* Database transaction utils.

View File

@ -2,7 +2,7 @@ package com.sismics.docs.core.util.action;
import com.sismics.docs.core.dao.dto.DocumentDto;
import javax.json.JsonObject;
import jakarta.json.JsonObject;
/**
* Base action interface.

View File

@ -6,7 +6,7 @@ import com.sismics.docs.core.dao.criteria.TagCriteria;
import com.sismics.docs.core.dao.dto.DocumentDto;
import com.sismics.docs.core.dao.dto.TagDto;
import javax.json.JsonObject;
import jakarta.json.JsonObject;
import java.util.List;
import java.util.Set;

View File

@ -13,7 +13,7 @@ import com.sismics.util.context.ThreadLocalContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.json.JsonObject;
import jakarta.json.JsonObject;
import java.nio.file.Path;
import java.util.List;
@ -48,7 +48,7 @@ public class ProcessFilesAction implements Action {
FileUpdatedAsyncEvent event = new FileUpdatedAsyncEvent();
event.setUserId("admin");
event.setLanguage(documentDto.getLanguage());
event.setFile(file);
event.setFileId(file.getId());
event.setUnencryptedFile(unencryptedFile);
ThreadLocalContext.get().addAsyncEvent(event);
}

View File

@ -6,7 +6,7 @@ import com.sismics.docs.core.dao.criteria.TagCriteria;
import com.sismics.docs.core.dao.dto.DocumentDto;
import com.sismics.docs.core.dao.dto.TagDto;
import javax.json.JsonObject;
import jakarta.json.JsonObject;
import java.util.List;
import java.util.Set;

View File

@ -4,7 +4,7 @@ import com.sismics.docs.core.dao.TagDao;
import com.sismics.docs.core.dao.criteria.TagCriteria;
import com.sismics.docs.core.dao.dto.TagDto;
import javax.json.JsonObject;
import jakarta.json.JsonObject;
import java.util.List;
/**

View File

@ -20,7 +20,7 @@ public class AuthenticationUtil {
.map(clazz -> {
try {
return clazz.newInstance();
return clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}

View File

@ -0,0 +1,107 @@
package com.sismics.docs.core.util.authentication;
import com.sismics.docs.core.constant.ConfigType;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.dao.ConfigDao;
import com.sismics.docs.core.dao.UserDao;
import com.sismics.docs.core.model.jpa.Config;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.util.ConfigUtil;
import com.sismics.util.ClasspathScanner;
import org.apache.directory.api.ldap.model.cursor.EntryCursor;
import org.apache.directory.api.ldap.model.entry.Attribute;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.entry.Value;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.ldap.client.api.LdapConnectionConfig;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.UUID;
/**
* LDAP authentication handler.
*
* @author bgamard
*/
@ClasspathScanner.Priority(50) // Before the internal database
public class LdapAuthenticationHandler implements AuthenticationHandler {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(LdapAuthenticationHandler.class);
/**
* Get a LDAP connection.
* @return LdapConnection
*/
private LdapConnection getConnection() {
ConfigDao configDao = new ConfigDao();
Config ldapEnabled = configDao.getById(ConfigType.LDAP_ENABLED);
if (ldapEnabled == null || !Boolean.parseBoolean(ldapEnabled.getValue())) {
return null;
}
LdapConnectionConfig config = new LdapConnectionConfig();
config.setLdapHost(ConfigUtil.getConfigStringValue(ConfigType.LDAP_HOST));
config.setLdapPort(ConfigUtil.getConfigIntegerValue(ConfigType.LDAP_PORT));
config.setUseSsl(ConfigUtil.getConfigBooleanValue(ConfigType.LDAP_USESSL));
config.setName(ConfigUtil.getConfigStringValue(ConfigType.LDAP_ADMIN_DN));
config.setCredentials(ConfigUtil.getConfigStringValue(ConfigType.LDAP_ADMIN_PASSWORD));
return new LdapNetworkConnection(config);
}
@Override
public User authenticate(String username, String password) {
// Fetch and authenticate the user
Entry userEntry;
try (LdapConnection ldapConnection = getConnection()) {
if (ldapConnection == null) {
return null;
}
EntryCursor cursor = ldapConnection.search(ConfigUtil.getConfigStringValue(ConfigType.LDAP_BASE_DN),
ConfigUtil.getConfigStringValue(ConfigType.LDAP_FILTER).replace("USERNAME", username), SearchScope.SUBTREE);
if (cursor.next()) {
userEntry = cursor.get();
ldapConnection.bind(userEntry.getDn(), password);
} else {
// User not found
return null;
}
} catch (Exception e) {
log.error("Error authenticating \"" + username + "\" using the LDAP", e);
return null;
}
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(username);
if (user == null) {
// The user is valid but never authenticated, create the user now
log.info("\"" + username + "\" authenticated for the first time, creating the internal user");
user = new User();
user.setRoleId(Constants.DEFAULT_USER_ROLE);
user.setUsername(username);
user.setPassword(UUID.randomUUID().toString()); // No authentication using the internal database
Attribute mailAttribute = userEntry.get("mail");
if (mailAttribute == null || mailAttribute.get() == null) {
user.setEmail(ConfigUtil.getConfigStringValue(ConfigType.LDAP_DEFAULT_EMAIL));
} else {
Value value = mailAttribute.get();
user.setEmail(value.getString());
}
user.setStorageQuota(ConfigUtil.getConfigLongValue(ConfigType.LDAP_DEFAULT_STORAGE));
try {
userDao.create(user, "admin");
} catch (Exception e) {
log.error("Error while creating the internal user", e);
return null;
}
}
return user;
}
}

View File

@ -26,12 +26,12 @@ public class FormatHandlerUtil {
public static FormatHandler find(String mimeType) {
try {
for (Class<? extends FormatHandler> formatHandlerClass : FORMAT_HANDLERS) {
FormatHandler formatHandler = formatHandlerClass.newInstance();
FormatHandler formatHandler = formatHandlerClass.getDeclaredConstructor().newInstance();
if (formatHandler.accept(mimeType)) {
return formatHandler;
}
}
} catch (InstantiationException | IllegalAccessException e) {
} catch (Exception e) {
return null;
}

View File

@ -6,6 +6,7 @@ import com.sismics.util.mime.MimeType;
import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.multipdf.PDFMergerUtility;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.text.PDFTextStripper;
import org.slf4j.Logger;
@ -60,7 +61,7 @@ public class PdfFormatHandler implements FormatHandler {
for (int pageIndex = 0; pageIndex < pdfDocument.getNumberOfPages(); pageIndex++) {
log.info("OCR page " + (pageIndex + 1) + "/" + pdfDocument.getNumberOfPages() + " of PDF file containing only images");
sb.append(" ");
sb.append(FileUtil.ocrFile(language, renderer.renderImage(pageIndex)));
sb.append(FileUtil.ocrFile(language, renderer.renderImageWithDPI(pageIndex, 300, ImageType.GRAY)));
}
return sb.toString();
} catch (Exception e) {

Some files were not shown because too many files have changed in this diff Show More