Compare commits

...
Author SHA1 Message Date
Philipp Schramm a4d41fc164 RED-4510: Generated Redaction Log and Generating will not run with profile 2022-07-26 13:10:59 +02:00
Philipp Schramm e1b8f5b89a RED-4510: Generated Redaction Log and Generating will not run with profile 2022-07-26 13:03:16 +02:00
Philipp Schramm 5834e8891f RED-4510: Generated Redaction Log and Generating will not run with profile 2022-07-26 08:54:20 +02:00
Timo Bejan 9c8e2f047e Pull request #432: RED-4686 - storage commons update
Merge in RED/redaction-service from RED-4686 to master

* commit '24612e347171f27eb8747772b82d13c98f40344b':
  RED-4686 - storage commons update - added annotations and test-file
  RED-4686 - storage commons update
2022-07-25 21:19:43 +02:00
Timo Bejan 24612e3471 RED-4686 - storage commons update - added annotations and test-file 2022-07-25 22:08:36 +03:00
Timo Bejan 9be8d46f4e RED-4686 - storage commons update 2022-07-25 21:45:58 +03:00
Philipp Schramm 3e32540616 Pull request #431: RED-4771: Reformatted code
Merge in RED/redaction-service from RED-4771 to master

* commit '5403829fd874c04375bef39535c22f52f7a55853':
  RED-4771: Reformatted code
2022-07-25 16:11:37 +02:00
Philipp Schramm 5403829fd8 Merge branch 'master' into RED-4771
# Conflicts:
#	bamboo-specs/src/main/java/buildjob/PlanSpec.java
2022-07-25 16:08:23 +02:00
Philipp Schramm 3e53e156a5 RED-4771: Reformatted code 2022-07-25 16:04:51 +02:00
Philipp Schramm 4c73d2841b Pull request #430: RED-4510: Added bamboo build to execute tests nightly and generate RedactionLog for tests if it doesn't exist
Merge in RED/redaction-service from RED-4510 to master

* commit 'db8eedc9a36dfba22946940b31e28854ffdca26d':
  RED-4510: Added bamboo build to execute tests nightly and generate RedactionLog for tests if it doesn't exist
2022-07-25 14:52:53 +02:00
Philipp Schramm db8eedc9a3 RED-4510: Added bamboo build to execute tests nightly and generate RedactionLog for tests if it doesn't exist 2022-07-25 14:14:44 +02:00
Dominique Eiflaender 58e16fe39a Pull request #429: RED-4686: Removed test scope from jackson-datatype-jsr310
Merge in RED/redaction-service from RED-4686-2 to master

* commit '0fe2b627f071f6d8e4841cf08e3bcdc059b60567':
  RED-4686: Removed test scope from jackson-datatype-jsr310
2022-07-22 15:10:33 +02:00
deiflaender 0fe2b627f0 RED-4686: Removed test scope from jackson-datatype-jsr310 2022-07-22 15:06:57 +02:00
Dominique Eiflaender e4b5bb93e1 Pull request #426: RED-4686: Stream objects to storage
Merge in RED/redaction-service from RED-4686 to master

* commit '99a8b9788e1b1062c8d8a93d4b5d4b65f3d34ce2':
  RED-4686: Stream objects to storage
2022-07-22 13:54:23 +02:00
deiflaender 99a8b9788e RED-4686: Stream objects to storage 2022-07-22 13:29:22 +02:00
Philipp Schramm 822e1f096f Pull request #425: RED-4510
Merge in RED/redaction-service from RED-4510 to master

* commit '8c5ede3fde0fd1b2df9e8fca0908e9233d6d403c':
  RED-4510: Refactoring after review
  RED-4510: Implemented Test class which generates and compares RedactionLogs
2022-07-21 17:01:18 +02:00
Philipp Schramm 8c5ede3fde RED-4510: Refactoring after review 2022-07-21 16:58:36 +02:00
Philipp Schramm 40380a12a3 Merge branch 'master' into RED-4510 2022-07-21 12:38:27 +02:00
Philipp Schramm f48bdb6267 RED-4510: Implemented Test class which generates and compares RedactionLogs 2022-07-21 12:38:07 +02:00
Dominique Eiflaender d3a5265b7f Pull request #424: RED-4648: Fixed wrong field names for DSLJson
Merge in RED/redaction-service from RED-4648 to master

* commit '708b7f992cadba8779ac6d2f7a9e9e4a837eaf1b':
  RED-4648: Fixed wrong field names for DSLJson
2022-07-21 12:33:42 +02:00
deiflaender 708b7f992c RED-4648: Fixed wrong field names for DSLJson 2022-07-21 12:22:19 +02:00
Ali Oezyetimoglu 2b0a667424 Pull request #422: RED-4548: Create SIMPLIFIED_TEXT.json from TEXT.json (for NER-service)
Merge in RED/redaction-service from RED-4548-rs2 to master

* commit 'c330de86246b8cc10955401d307d7d1a20190e32':
  RED-4548: Create SIMPLIFIED_TEXT.json from TEXT.json (for NER-service)
2022-07-14 17:08:03 +02:00
Ali Oezyetimoglu c330de8624 RED-4548: Create SIMPLIFIED_TEXT.json from TEXT.json (for NER-service) 2022-07-14 16:57:09 +02:00
Timo Bejan 32e73dc9d9 Pull request #420: RED-4593
Merge in RED/redaction-service from tbejan/pomxml-1657693418623 to master

* commit '5e61f3370dcec8206b757dd59a1a7664921c8300':
  RED-4593
2022-07-13 08:32:03 +02:00
Timo Bejan 5e61f3370d RED-4593 2022-07-13 08:23:59 +02:00
Philipp Schramm bfadac7a3f Pull request #419: RED-4548: Create SIMPLIFIED_TEXT.json from TEXT.json (for NER-service)
Merge in RED/redaction-service from RED-4548 to master

* commit 'df80ff68cd21626679c107b056b275b67ce6bf74':
  RED-4548: Create SIMPLIFIED_TEXT.json from TEXT.json (for NER-service)
2022-07-12 16:41:06 +02:00
Philipp Schramm df80ff68cd RED-4548: Create SIMPLIFIED_TEXT.json from TEXT.json (for NER-service) 2022-07-12 16:38:25 +02:00
Timo Bejan 13352e7d16 Pull request #418: RED-4525 - Colors update
Merge in RED/redaction-service from RED-4525 to master

* commit '3c9d59f73ba2ca2b7500703b57df6d78b558d6a2':
  RED-4525 - Colors update
2022-07-11 20:58:27 +02:00
Timo Bejan 3c9d59f73b RED-4525 - Colors update 2022-07-11 21:55:29 +03:00
Dominique Eiflaender 333793021a Pull request #417: RED-4254: Added dirty hack in pdfbox classes to find words that contains uniqueCharacters with 2 chars like 'RA'
Merge in RED/redaction-service from RED-4254 to master

* commit '264d7e3a87d4a99aa60a4c7c140335ba8d3a7241':
  RED-4254: Added dirty hack in pdfbox classes to find words that contains uniqueCharacters with 2 chars like 'RA'
2022-06-28 15:30:02 +02:00
deiflaender 264d7e3a87 RED-4254: Added dirty hack in pdfbox classes to find words that contains uniqueCharacters with 2 chars like 'RA' 2022-06-28 15:11:33 +02:00
Dominique Eiflaender 6b89572c87 Pull request #416: RED-4407: Fixed merging request for dossierDictionarys in redactionLog
Merge in RED/redaction-service from RED-4407 to master

* commit 'a74d049cf8a52395771f9940471defd692cae0e8':
  RED-4407: Fixed merging request for dossierDictionarys in redactionLog
2022-06-28 11:55:12 +02:00
deiflaender a74d049cf8 RED-4407: Fixed merging request for dossierDictionarys in redactionLog 2022-06-28 11:52:06 +02:00
Ali Oezyetimoglu 76dc08d0e6 Pull request #415: RED-4350: Reason set to null after reject suggestion "Remove from Dict" for skipped types
Merge in RED/redaction-service from RED-4350-rs1 to master

* commit '663c361707678370149ed08cb6c33c8ac341dc26':
  RED-4350: Reason set to null after reject suggestion "Remove from Dict" for skipped types
2022-06-28 09:01:05 +02:00
aoezyetimoglu 663c361707 RED-4350: Reason set to null after reject suggestion "Remove from Dict" for skipped types 2022-06-27 17:43:28 +02:00
Dominique Eiflaender c81b062784 Pull request #413: RED-4301: Revert redaction log merge of duplicate annotations
Merge in RED/redaction-service from RED-4301 to master

* commit '57ef7ead72018a91d9c15530efce40f713fb6c86':
  RED-4301: Revert redaction log merge of duplicate annotations
2022-06-20 12:50:53 +02:00
deiflaender 57ef7ead72 RED-4301: Revert redaction log merge of duplicate annotations 2022-06-20 12:38:57 +02:00
Dominique Eiflaender 3269470446 Pull request #412: RED-4129: Fixed imported redactions annotations intersection calculation
Merge in RED/redaction-service from RED-4129 to master

* commit 'c7e10cefbacf8ca54c86ecba5a8598b7c9d55ce4':
  RED-4129: Fixed imported redactions annotations intersection calculation
2022-06-20 11:59:58 +02:00
deiflaender c7e10cefba RED-4129: Fixed imported redactions annotations intersection calculation 2022-06-20 11:57:10 +02:00
Timo Bejan 2301849815 Pull request #405: RED-3791 - redaction log merge of duplicate annotations
Merge in RED/redaction-service from RED-3791 to master

* commit 'ec8d3183dc119e3eddd45a653ca88a2666073f51':
  RED-3791 - redaction log merge of duplicate annotations
2022-06-14 18:25:35 +02:00
Dominique Eiflaender 2d5ba8f447 Pull request #411: RED-4129: Fixed calculation of imported redaction intersections for all rotation cases
Merge in RED/redaction-service from RED-4129 to master

* commit '2a6ee02f9f8f527546f01cf98542dfc7e39b259a':
  RED-4129: Fixed calculation of imported redaction intersections for all rotation cases
2022-06-14 12:15:01 +02:00
deiflaender 2a6ee02f9f RED-4129: Fixed calculation of imported redaction intersections for all rotation cases 2022-06-14 12:10:25 +02:00
Dominique Eiflaender 7dad091e19 Pull request #409: RED-3805: Added transpacency to images
Merge in RED/redaction-service from RED-3805 to master

* commit 'f7b584617bd5bc7b2d71e44c407b0d519c1f5a75':
  RED-3805: Added transpacency to images
2022-06-10 13:27:16 +02:00
deiflaender f7b584617b RED-3805: Added transpacency to images 2022-06-10 13:24:14 +02:00
Dominique Eiflaender 02a264d586 Pull request #408: RED-4114: Added all missing cases for text rotations
Merge in RED/redaction-service from RED-4114 to master

* commit 'dacd7b725d1bcdd17193aaefa045bd9555b8528a':
  RED-4114: Added all missing cases for text rotations
2022-06-10 12:20:16 +02:00
deiflaender dacd7b725d RED-4114: Added all missing cases for text rotations 2022-06-10 12:15:53 +02:00
Dominique Eiflaender 7ee338a067 Pull request #407: RED-4176: Only apply APPROVED resizes in to annotations in analysis
Merge in RED/redaction-service from RED-4176 to master

* commit '19d48e207b6fb4169adb894ced089fb3c2f9c49f':
  RED-4176: Only apply APPROVED resizes in to annotations in analysis
2022-06-09 12:11:28 +02:00
deiflaender 19d48e207b RED-4176: Only apply APPROVED resizes in to annotations in analysis 2022-06-09 12:08:21 +02:00
Dominique Eiflaender e22b625e9c Pull request #406: RED-4114: Fixed text rotation for rotation = 0 and text direction = 180
Merge in RED/redaction-service from RED-4114 to master

* commit '95a2d71af0a69c06a40e4d39d1f9f1286d726028':
  RED-4114: Fixed text rotation for rotation = 0 and text direction = 180
2022-06-09 09:58:46 +02:00
deiflaender 95a2d71af0 RED-4114: Fixed text rotation for rotation = 0 and text direction = 180 2022-06-09 09:55:46 +02:00
Timo Bejan ec8d3183dc RED-3791 - redaction log merge of duplicate annotations 2022-06-09 09:26:00 +03:00
Corina Olariu 6fec4cc209 Pull request #404: RED-4029 - Do not log on ERROR level if something happens that is expected
Merge in RED/redaction-service from RED_4029_1 to master

* commit 'fa3cbc938c6d5066b4c6706bc0c98ac030f4c498':
  RED-4029 - Do not log on ERROR level if something happens that is expected - put back null pointer exception to error logging
2022-06-07 10:49:18 +02:00
devplant fa3cbc938c RED-4029 - Do not log on ERROR level if something happens that is expected
- put back null pointer exception to error logging
2022-06-07 11:39:55 +03:00
Dominique Eiflaender f6b4d2a067 Pull request #403: RED-4014: Renamed wrong metrics name
Merge in RED/redaction-service from RED-4014 to master

* commit '0967e8e64a99654733df1e860d297e03587c7dea':
  RED-4014: Renamed wrong metrics name
2022-06-07 10:27:23 +02:00
deiflaender 0967e8e64a RED-4014: Renamed wrong metrics name 2022-06-07 10:14:59 +02:00
Dominique Eiflaender 20c7e19343 Pull request #402: RED-4014: Renamed and added metrics
Merge in RED/redaction-service from RED-4014 to master

* commit '6fb7600de2ab7a00d6071de3c701c3a7410e8fb7':
  RED-4014: Renamed and added metrics
2022-06-07 09:33:06 +02:00
deiflaender 6fb7600de2 RED-4014: Renamed and added metrics 2022-06-07 09:28:38 +02:00
Corina Olariu fbd55c15ef Pull request #401: RED-4029 - Do not log on ERROR level if something happens that is expected
Merge in RED/redaction-service from RED-4029 to master

* commit 'ffa251643a2e4d711bac01469d31e09c3c8b8290':
  RED-4029 - Do not log on ERROR level if something happens that is expected - replace error logs
2022-06-06 20:50:27 +02:00
devplant ffa251643a RED-4029 - Do not log on ERROR level if something happens that is expected
- replace error logs
2022-06-03 15:31:00 +03:00
Dominique Eiflaender ac19defa1c Pull request #400: RED-4064: Add resize redactions sections to reanalysis sections
Merge in RED/redaction-service from RED-4064 to master

* commit '21ca25f3309f0706c1d708eb77724824ef10168d':
  RED-4064: Add resize redactions sections to reanalysis sections
2022-06-03 12:09:17 +02:00
deiflaender 21ca25f330 RED-4064: Add resize redactions sections to reanalysis sections 2022-06-03 12:01:05 +02:00
Dominique Eiflaender e9b9fbb91b Pull request #399: RED-4064: Apply all resize redactions directly after find, calculate surrounding text in renalysis
Merge in RED/redaction-service from RED-4064 to master

* commit '3f76401c7d051ee100eab8a36847c2e8a5426900':
  RED-4064: Apply all resize redactions directly after find, calculate surrounding text in renalysis
2022-06-03 10:54:49 +02:00
deiflaender 3f76401c7d RED-4064: Apply all resize redactions directly after find, calculate surrounding text in renalysis 2022-06-03 10:51:21 +02:00
Dominique Eiflaender a16f9c6d6e Pull request #398: RED-4064: Fixed override logic for resized entries
Merge in RED/redaction-service from RED-4064 to master

* commit '7ca3416d1d809189430540603034b67dfddd5bcd':
  RED-4064: Fixed override logic for resized entries
2022-06-02 12:43:07 +02:00
deiflaender 7ca3416d1d RED-4064: Fixed override logic for resized entries 2022-06-02 12:40:17 +02:00
Dominique Eiflaender 85db826f89 Pull request #397: RED-4064: Apply resize redactions also in redactCell
Merge in RED/redaction-service from RED-4064 to master

* commit '6fac40fd8dea79c5575cee2b480269d43baaef7a':
  RED-4064: Apply resize redactions also in redactCell
2022-06-02 11:30:46 +02:00
deiflaender 6fac40fd8d RED-4064: Apply resize redactions also in redactCell 2022-06-02 11:27:26 +02:00
Dominique Eiflaender 23fe93630b Pull request #396: RED-4064: Fixed find dictionary entries at position where rules entries are resized to smaller entries
Merge in RED/redaction-service from RED-4064 to master

* commit '778c9756495f14ead45fadd8b906f4301e8b3ec7':
  RED-4064: Fixed find dictionary entries at position where rules entries are resized to smaller entries
2022-06-02 10:41:27 +02:00
deiflaender 778c975649 RED-4064: Fixed find dictionary entries at position where rules entries are resized to smaller entries 2022-06-02 10:35:38 +02:00
Philipp Schramm 50b2908763 Pull request #395: RED-3816: Bugfix with adding images to sections
Merge in RED/redaction-service from bugfix/RED-3816 to master

* commit 'c1192ceefd0717958badc27d2c2a65a8059952a4':
  RED-3816: Bugfix with adding images to sections
2022-05-30 14:43:35 +02:00
Philipp Schramm c1192ceefd RED-3816: Bugfix with adding images to sections 2022-05-30 13:52:57 +02:00
Timo Bejan 77584c9a5a Pull request #394: RED-3800 String Performance matching test
Merge in RED/redaction-service from RED-3800-string-performance-test to master

* commit '21d717f0837c9cc9b2f5b251d372db88b25a7b6d':
  RED-3800 String Performance matching test
  RED-3800 String Performance matching test
2022-05-24 11:09:08 +02:00
Timo Bejan 21d717f083 RED-3800 String Performance matching test 2022-05-24 12:01:10 +03:00
Timo Bejan c85ce25ed4 RED-3800 String Performance matching test 2022-05-24 11:58:34 +03:00
Philipp Schramm f608d991a3 Pull request #393: RED-3816: Implemented ThenAction to redact section with rectangle
Merge in RED/redaction-service from RED-3816 to master

* commit 'eb7137315208dadf3a679e21b05ec16d53c4ec43':
  RED-3816: Implemented ThenAction to redact section with rectangle
2022-05-24 09:00:07 +02:00
Ali Oezyetimoglu e914b09d1e Pull request #392: RED-3674: Fixed force redactions overrides idRemoval with different id
Merge in RED/redaction-service from RED-3674-rs1 to master

* commit '87d64b6d1285eaf7cd5ee3c2fb899f92063bc70f':
  RED-3674: Fixed force redactions overrides idRemoval with different id
2022-05-23 18:48:10 +02:00
aoezyetimoglu 87d64b6d12 RED-3674: Fixed force redactions overrides idRemoval with different id 2022-05-23 17:38:23 +02:00
Philipp Schramm eb71373152 Merge branch 'master' into RED-3816 2022-05-23 16:27:12 +02:00
Philipp Schramm a65e23e45d RED-3816: Implemented ThenAction to redact section with rectangle 2022-05-23 16:26:47 +02:00
Dominique Eiflaender 4aaef260d7 Pull request #390: RED-4075: Add legalBasis if Rule Entity overrides Ai Recommendation
Merge in RED/redaction-service from RED-4075 to master

* commit 'c669f9a09a3e5e254d41715f65fef3b158873338':
  RED-4075: Add legalBasis if Rule Entity overrides Ai Recommendation
2022-05-23 14:05:38 +02:00
deiflaender c669f9a09a RED-4075: Add legalBasis if Rule Entity overrides Ai Recommendation 2022-05-23 13:54:27 +02:00
deiflaender b1bad03090 RED-4014: Changed metrics config 2022-05-12 16:08:54 +02:00
deiflaender 0a0d9fe31a RED-4014: Added names to @Timed annotations 2022-05-12 15:56:03 +02:00
deiflaender fd37e3deab RED-4014: Added @Timed annotations to AnaylseService 2022-05-12 15:10:35 +02:00
Dominique Eiflaender 7fb04cfa48 Pull request #389: RED-4010: Fixed problem with strange unicode in json d835
Merge in RED/redaction-service from RED-4010 to master

* commit '0f5f7974cc46e36d39d514ad03b5adcf062c9526':
  RED-4010: Fixed problem with strange unicode in json d835
2022-05-11 14:41:07 +02:00
deiflaender 0f5f7974cc RED-4010: Fixed problem with strange unicode in json d835 2022-05-11 14:38:06 +02:00
Dominique Eiflaender 0601ec4862 Pull request #388: RED-4009: Fixed NPE if comments are null in RedactionLogMergeService
Merge in RED/redaction-service from RED-4009 to master

* commit 'd2807e0ff1ef288c7456383974a8607a8fca4821':
  RED-4009: Fixed NPE if comments are null in RedactionLogMergeService
2022-05-10 13:24:23 +02:00
deiflaender d2807e0ff1 RED-4009: Fixed NPE if comments are null in RedactionLogMergeService 2022-05-10 13:20:57 +02:00
Timo Bejan e49bbfca88 Pull request #387: RED-3800 switched jackson for dsl-json for internal non-rest related operations
Merge in RED/redaction-service from RED-3800-dsljson to master

* commit '7967453c676094efdc438eea1cca2b9e00856c0d':
  RED-3800 api cleanup
  RED-3800 api cleanup
  RED-3800 switched jackson for dsl-json for internal non-rest related operations
2022-05-09 21:14:04 +02:00
Timo Bejan 7967453c67 RED-3800 api cleanup 2022-05-09 22:10:53 +03:00
Timo Bejan a3d002929d RED-3800 api cleanup 2022-05-09 22:03:33 +03:00
Timo Bejan 8a44d6299c RED-3800 switched jackson for dsl-json for internal non-rest related operations 2022-05-09 22:00:01 +03:00
Timo Bejan 78de7371a0 Pull request #385: RED-3946 - quotation-mark fix
Merge in RED/redaction-service from RED-3946-qf to master

* commit 'df2fcd2e5e48298131ebf7a00bf2ed80a9fc2d96':
  RED-3946 - quotation-mark fix
2022-05-09 14:47:03 +02:00
Dominique Eiflaender 924f791e4d Pull request #386: RED-3959: Do not remove entities on larger false_recommendactions
Merge in RED/redaction-service from RED-3959 to master

* commit '542bbb294a27bf2eb2832a144d239b820bbc0b5f':
  RED-3959: Do not remove entities on larger false_recommendactions
2022-05-09 12:34:13 +02:00
deiflaender 542bbb294a RED-3959: Do not remove entities on larger false_recommendactions 2022-05-09 12:27:36 +02:00
Timo Bejan df2fcd2e5e RED-3946 - quotation-mark fix 2022-05-09 12:14:09 +03:00
Timo Bejan 8e819a0d26 Pull request #384: RED-3992
Merge in RED/redaction-service from RED-3992 to master

* commit '01e7115d301d8706962390b76af0769eab67c1fe':
  RED-3992 - Fixed PMD
  RED-3992 - Replaced naive approach with aho corassick string search. Cleaned up code. reduced ammount of unnecesary conversions/invocations
  RED-3992 - Replaced naive approach with aho corassick string search. Cleaned up code. reduced ammount of unnecesary conversions/invocations
  RED-3800 Exception handling for processed files
2022-05-09 10:05:50 +02:00
Timo Bejan 01e7115d30 RED-3992 - Fixed PMD 2022-05-09 09:02:11 +03:00
Timo Bejan 747323f882 RED-3992 - Replaced naive approach with aho corassick string search. Cleaned up code. reduced ammount of unnecesary conversions/invocations 2022-05-09 08:44:42 +03:00
Timo Bejan 729a7334d9 Merge branch 'release/3.107.x' into test-performance 2022-05-06 10:28:47 +03:00
Timo Bejan 36967cab2c RED-3992 - Replaced naive approach with aho corassick string search. Cleaned up code. reduced ammount of unnecesary conversions/invocations 2022-05-06 10:28:12 +03:00
Timo Bejan b820463fff RED-3800 Exception handling for processed files 2022-05-04 12:09:46 +03:00
Timo Bejan cfd9d7665b Pull request #383: RED-3946 - added alternative quatation marks as separators
Merge in RED/redaction-service from RED-3946 to master

* commit '23d84980cd1528b1276e28aab81bb5bf0a682256':
  RED-3946 - added alternative quatation marks as separators
2022-05-03 17:15:12 +02:00
Timo Bejan 23d84980cd RED-3946 - added alternative quatation marks as separators 2022-05-03 18:12:07 +03:00
Timo Bejan 314f7ebdd9 Pull request #382: Pdftron api cleanup
Merge in RED/redaction-service from pdftron-api-cleanup to master

* commit 'f666a0330161daed8d3f59189bbe258327926995':
  RED-3800 code cleanup
  RED-3800 code cleanup
2022-05-03 12:26:22 +02:00
Timo Bejan f666a03301 RED-3800 code cleanup 2022-05-03 13:23:10 +03:00
Timo Bejan 313ec32d30 RED-3800 code cleanup 2022-05-03 13:04:00 +03:00
Timo Bejan 76dca41299 Pull request #381: RED-3915 - source id
Merge in RED/redaction-service from RED-3915 to master

* commit 'f21e48e3d75c78e0ce5cf92a614e92e9c721eede':
  RED-3915 - source id
2022-04-25 15:03:29 +02:00
Timo Bejan f21e48e3d7 RED-3915 - source id 2022-04-25 15:46:12 +03:00
Dominique Eiflaender bbd60e4bf4 Pull request #379: RED-3774: Always prefer entities over recommendactions
Merge in RED/redaction-service from RED-3774 to master

* commit 'ecf794ff37ae5cdeb8aa63f69d6e5205c6da1271':
  RED-3774: Always prefer entities over recommendactions
2022-04-20 12:20:01 +02:00
deiflaender ecf794ff37 RED-3774: Always prefer entities over recommendactions 2022-04-20 12:07:31 +02:00
Dominique Eiflaender 5d8f44d55d Pull request #378: RED-3658: Fixed annotation positions for document with rotation = 180 and text direction = 180
Merge in RED/redaction-service from RED-3658 to master

* commit '513fbbb38d2b9efff6b58489c518db53961e9464':
  RED-3658: Fixed annotation positions for document with rotation = 180 and text direction = 180
2022-04-20 10:17:34 +02:00
deiflaender 513fbbb38d RED-3658: Fixed annotation positions for document with rotation = 180 and text direction = 180 2022-04-20 09:58:26 +02:00
Timo Bejan 6178efb7ef Pull request #376: soft delete types RED-3880
Merge in RED/redaction-service from RED-3880 to master

* commit '7a9bb45b6dbfde2ef68c4ac19d4a144a272e5571':
  soft delete types RED-3880
  soft delete types RED-3880
2022-04-19 14:01:31 +02:00
Timo Bejan 7a9bb45b6d soft delete types RED-3880 2022-04-19 14:54:44 +03:00
Timo Bejan 00f2a0cb82 soft delete types RED-3880 2022-04-19 14:32:23 +03:00
Timo Bejan 1eb0485dca Pull request #375: RED-3800 java-18 support
Merge in RED/redaction-service from JAVA-18-support to master

* commit 'be15d38065fc6915f815a586d468235b38362e93':
  RED-3800 java-18 support
2022-04-14 10:52:02 +02:00
Timo Bejan be15d38065 RED-3800 java-18 support 2022-04-14 11:44:47 +03:00
deiflaender f154065824 RED-3877: Fixed missing images after migration 2022-04-14 10:38:30 +02:00
Dominique Eiflaender d4b0de9184 Pull request #374: RED-3616: Fixed removing not redaction images that intersects imported redactions
Merge in RED/redaction-service from RED-3616-11 to master

* commit 'fbd9bc71a80ba7788992c6213fd88ce649e127e2':
  RED-3616: Fixed removing not redaction images that intersects imported redactions
2022-04-12 11:18:53 +02:00
deiflaender fbd9bc71a8 RED-3616: Fixed removing not redaction images that intersects imported redactions 2022-04-12 11:09:25 +02:00
Dominique Eiflaender c963c7ac32 Pull request #373: RED-3666: Added rule function Section containsRegEx
Merge in RED/redaction-service from RED-3666 to master

* commit '404fd25be0e8ee20528c1f9491f85d8b695a2433':
  RED-3666: Added rule function Section containsRegEx
2022-04-11 13:02:02 +02:00
deiflaender 404fd25be0 RED-3666: Added rule function Section containsRegEx 2022-04-11 12:57:57 +02:00
Kresnadi Budisantoso efd53066c4 Pull request #371: RED-3761 Fix expandByRegEx and expandByPrefixRegEx
Merge in RED/redaction-service from kbudisantoso/RED-3761-fix-filter-expanded to master

* commit '5b5d898b933c3ae79fb7cb07a6105b15d2952f8e':
  RED-3761 Fix expandByRegEx and expandByPrefixRegEx
2022-04-11 12:51:57 +02:00
Kresnadi Budisantoso 5b5d898b93 RED-3761 Fix expandByRegEx and expandByPrefixRegEx
Adjusted the overlap test to ignore if the compared entities are subsets
of each other (start or end position is equal). The `removeEntitiesContainedInLarger`
instruction will remove the subset entity.
2022-04-11 11:54:30 +02:00
Dominique Eiflaender cf75ac56cc Pull request #372: RED-3802: Fixed prefer redactions over recommendations
Merge in RED/redaction-service from RED-3802 to master

* commit '1a6a1a008f76897f01f10dd40ea0307c2c84175c':
  RED-3802: Fixed prefer redactions over recommendations
2022-04-11 11:04:55 +02:00
deiflaender 1a6a1a008f RED-3802: Fixed prefer redactions over recommendations 2022-04-11 11:00:07 +02:00
Ali Oezyetimoglu e19b4573bb Pull request #370: RED-3809: Upgrade to latest spring version
Merge in RED/redaction-service from RED-3809-rs1 to master

* commit '150ee92c1e63de2145a1a093e6c6fe7820b7256d':
  RED-3809: Upgrade to latest spring version
2022-04-08 11:40:40 +02:00
aoezyetimoglu 150ee92c1e RED-3809: Upgrade to latest spring version 2022-04-08 11:19:53 +02:00
Kresnadi Budisantoso 7988a8de66 Pull request #369: RED-3761 prevent compiledPrefixPattern to be nullable by testing prefixPattern parameter
Merge in RED/redaction-service from kbudisantoso/RED-3761-prevent-nullable to master

* commit '0014d7725ab3f82d2fba4f0677c3966324504876':
  RED-3761 prevent compiledPrefixPattern to be nullable by testing prefixPattern parameter
2022-04-06 12:52:24 +02:00
Kresnadi Budisantoso 0014d7725a RED-3761 prevent compiledPrefixPattern to be nullable by testing prefixPattern parameter 2022-04-06 11:38:20 +02:00
Christoph Schabert 55f608cf99 Pull request #368: verify version finding
Merge in RED/redaction-service from autoRelease to master

* commit '52e5c58d6e2a30744b3c9b5220edb102b457c8b0':
  verify version finding
2022-04-06 11:03:54 +02:00
cschabert f3ad0749b8 add build version output 2022-04-05 11:15:37 +02:00
Dominique Eiflaender 0f0aee9480 Pull request #367: RED-3616: Do not remove annotation that intersects with skipped imported redactions
Merge in RED/redaction-service from RED-3616 to master

* commit '941448492bfe294634a8c3c309f237034245e842':
  RED-3616: Do not remove annotation that intersects with skipped imported redactions
2022-04-05 10:22:08 +02:00
cschabert ff22dd1a49 verify version check 2022-04-05 09:26:05 +02:00
cschabert 52e5c58d6e verify version finding 2022-04-05 09:22:31 +02:00
deiflaender 941448492b RED-3616: Do not remove annotation that intersects with skipped imported redactions 2022-04-05 08:59:18 +02:00
Christoph Schabert 49d47a0682 Pull request #365: Change Major Version to 3.x
Merge in RED/redaction-service from cschabert/pomxml-1649055501527 to master

* commit '9bab1205b711d94888a6b3364a41a89d07d310b6':
  Change Major Version to 3.x
2022-04-04 09:21:17 +02:00
Christoph Schabert 9bab1205b7 Change Major Version to 3.x 2022-04-04 08:58:35 +02:00
Kresnadi Budisantoso 7963c57552 Pull request #364: RED-3761 add possibility to expand dictionary match with a prefix regex pattern
Merge in RED/redaction-service from kbudisantoso/RED-3761-expandByPrefixRegEx to master

* commit '1427087d8fa29a218b8b378f83c345c497a26b77':
  RED-3761 add possibility to expand dictionary match with a prefix regex pattern
2022-04-01 15:35:45 +02:00
Kresnadi Budisantoso 1427087d8f RED-3761 add possibility to expand dictionary match with a prefix regex pattern 2022-04-01 14:21:47 +02:00
Christoph Schabert b83400ffa3 Pull request #362: automate releases
Merge in RED/redaction-service from autoRelease to master

* commit 'f05f2b21a46b8a4f15bd933570d5a107351aa7b7':
  automate releases
2022-04-01 14:12:09 +02:00
cschabert f05f2b21a4 automate releases 2022-04-01 13:47:26 +02:00
Philipp Schramm 02d9ebc24c Pull request #360: RED-3568: Do not ignore textblocks if only rotated pages are in the document
Merge in RED/redaction-service from RED-3568 to master

* commit 'ae3e4570c15dbc2c46373ce68d55cec760e7da6b':
  RED-3568: Do not ignore textblocks if only rotated pages are in the document
2022-03-29 14:00:20 +02:00
deiflaender ae3e4570c1 RED-3568: Do not ignore textblocks if only rotated pages are in the document 2022-03-29 13:54:23 +02:00
Dominique Eiflaender a975596446 Pull request #359: RED-3699 master
Merge in RED/redaction-service from RED-3699-master to master

* commit 'a5b96f148950e28618028daef1e91aecdadc89e3':
  Fixed unknown type for manual type
  Fixed bug in getColors
  RED-3699:  Do not call updateDictionary on merge RedactionLog
2022-03-25 09:20:52 +01:00
deiflaender a5b96f1489 Fixed unknown type for manual type 2022-03-25 09:12:32 +01:00
deiflaender 4814ce3650 Fixed bug in getColors 2022-03-25 09:12:21 +01:00
deiflaender 7fccdb32c5 RED-3699: Do not call updateDictionary on merge RedactionLog 2022-03-25 09:12:00 +01:00
Timo Bejan 1d389b78fd Pull request #357: processed date fix
Merge in RED/redaction-service from processed-date-fix-3.3.x to master

* commit 'b4916b65b661c80f0924c163b8795205b9c8e6c3':
  processed date fix
2022-03-24 11:52:29 +01:00
Timo Bejan b4916b65b6 processed date fix 2022-03-24 10:16:49 +02:00
Dominique Eiflaender e1db383463 Pull request #356: RED-2836: Fixed calculating dictionary increments with false_positive and false_recommendation
Merge in RED/redaction-service from RED-2836-3 to master

* commit '4d2a7d454d916e7eee65f7cc155323924c2cb229':
  RED-2836: Fixed calculating dictionary increments with false_positive and false_recommendation
2022-03-22 10:31:05 +01:00
deiflaender 4d2a7d454d RED-2836: Fixed calculating dictionary increments with false_positive and false_recommendation 2022-03-22 10:24:26 +01:00
Dominique Eiflaender 3758720683 Pull request #355: Renamed result field in research services to data
Merge in RED/redaction-service from ResearchRename to master

* commit '22bb0b44db7a22f8088512570d4b2be6e742a887':
  Renamed result field in research services to data
2022-03-21 16:02:45 +01:00
deiflaender 22bb0b44db Renamed result field in research services to data 2022-03-21 15:54:37 +01:00
Dominique Eiflaender e93f5d147f Pull request #354: RED-2836: Enabled false positives per dictionary
Merge in RED/redaction-service from RED-2836 to master

* commit '88e6ac3d2291ced8bb2229c3e7daeb8d78d6da93':
  RED-2836: Enabled false positives per dictionary
2022-03-21 12:54:48 +01:00
deiflaender 88e6ac3d22 RED-2836: Enabled false positives per dictionary 2022-03-21 12:47:27 +01:00
Timo Bejan 17b885d901 Pull request #353: pending add to dict
Merge in RED/redaction-service from pending-master-fix to master

* commit '50d9f4fec22768310232988ee01f9e9aa3ba9dc8':
  pending add to dict
2022-03-21 11:49:16 +01:00
Timo Bejan 50d9f4fec2 pending add to dict 2022-03-21 11:19:26 +02:00
Philipp Schramm 1bcf0a3f07 Pull request #351: RED-3553 If image overlaps entity and the entity will be removed
Merge in RED/redaction-service from RED-3553 to master

* commit '1f6a9aa14deb7ec65fee03e28758f8e3157dbd5f':
  RED-3553 If image overlaps entity and the entity will be removed
2022-03-17 14:17:16 +01:00
Philipp Schramm 1f6a9aa14d RED-3553 If image overlaps entity and the entity will be removed 2022-03-17 14:04:59 +01:00
Dominique Eiflaender c8cf0078eb Pull request #352: PortsFrom3.2.x
Merge in RED/redaction-service from portsFrom3.2.x to master

* commit 'b13a695c64280a97ece198a18605c0f1b5cf753f':
  RED-3611: Added priority mode that only listens to priority queue
  RED-3620: Fixed remove from dictionary
2022-03-17 11:16:03 +01:00
deiflaender b13a695c64 RED-3611: Added priority mode that only listens to priority queue 2022-03-17 10:50:48 +01:00
deiflaender 32a907f697 RED-3620: Fixed remove from dictionary 2022-03-17 10:49:01 +01:00
Timo Bejan 1d1de6caba Pull request #350: Removed processed date filter for add to dictionary entries
Merge in RED/redaction-service from RED-3538 to master

* commit '7b2295393c1c2dcbcecbb11c64929517cb13a6de':
  Removed processed date filter for add to dictionary entries
2022-03-16 15:46:13 +01:00
Timo Bejan 7b2295393c Removed processed date filter for add to dictionary entries 2022-03-16 16:29:30 +02:00
Timo Bejan d0ba3f51c1 Pull request #347: From version master
Merge in RED/redaction-service from from-version-master to master

* commit 'c432c56a05ce58d711286bbbcc636ff1445c652d':
  Updated Persistence Service Version
  Release 3.80.x branch creation + incremental entry loading based on version for dictionaries
2022-03-14 16:36:14 +01:00
Timo Bejan c432c56a05 Updated Persistence Service Version 2022-03-14 17:29:04 +02:00
Timo Bejan e73da002d5 Release 3.80.x branch creation + incremental entry loading based on version for dictionaries 2022-03-14 17:27:52 +02:00
Philipp Schramm 0ceb8ae266 Pull request #346: RED-3403 Extended Section with negated Methods
Merge in RED/redaction-service from RED-3403 to master

* commit '97fe7383cd9737f25d22903a6124dfe8361f7a84':
  RED-3403 Extended Section with negated Methods
2022-02-28 14:51:21 +01:00
Philipp Schramm 97fe7383cd RED-3403 Extended Section with negated Methods 2022-02-28 14:40:40 +01:00
Dominique Eiflaender 7d132cbb88 Pull request #345: Fixed aws s3 config
Merge in RED/redaction-service from s3FixConf to master

* commit 'd18b5dfd138595f0cbf7cc2a99dc708994141425':
  Fixed aws s3 config
2022-02-25 13:04:36 +01:00
deiflaender d18b5dfd13 Fixed aws s3 config 2022-02-25 12:58:09 +01:00
Dominique Eiflaender e2451a08f4 Pull request #344: RED-3282: Do not add overlapping ai recommendactions
Merge in RED/redaction-service from RED-3282 to master

* commit '537ccb83d799cef0eb437a6bbe1b78da661b2b6b':
  RED-3282: Do not add overlapping ai recommendactions
2022-02-25 09:37:15 +01:00
deiflaender 537ccb83d7 RED-3282: Do not add overlapping ai recommendactions 2022-02-25 09:29:08 +01:00
Ali Oezyetimoglu fd027efa5c Pull request #343: RED-3407: Remove obsolete recommendation dictionaries
Merge in RED/redaction-service from RED-3407-rs1 to master

* commit 'c3984e054482acab3acde740b8dce437d97e9ffb':
  RED-3407: Remove obsolete recommendation dictionaries
2022-02-23 14:59:53 +01:00
aoezyetimoglu c3984e0544 RED-3407: Remove obsolete recommendation dictionaries 2022-02-23 14:29:01 +01:00
Timo Bejan 77feedcc8e Pull request #342: added imported flag
Merge in RED/redaction-service from imported-redaction to master

* commit 'de92117b35fcc2f3979450027acf1f7edf04befa':
  added imported flag
2022-02-11 11:41:32 +01:00
Timo Bejan de92117b35 added imported flag 2022-02-11 11:33:34 +02:00
Philipp Schramm 143f4cfb0d Pull request #341: RED-3350 Communication with entity recognition via queues
Merge in RED/redaction-service from feature/RED-3350 to master

* commit '68727178e79224b7ca7f9f293e48ca21b4ce2f91':
  RED-3350 Communication with entity recognition via queues
2022-02-09 15:24:47 +01:00
Philipp Schramm 68727178e7 RED-3350 Communication with entity recognition via queues 2022-02-09 15:19:55 +01:00
Dominique Eiflaender 90fe282313 Pull request #339: RED-3327: Ignore recommendations if touches other type
Merge in RED/redaction-service from RED-3327-master to master

* commit '8689125ed9b09f73781d3babfd65537c0b1f4afe':
  RED-3327: Ignore recommendations if touches other type
2022-02-01 11:19:26 +01:00
deiflaender 8689125ed9 RED-3327: Ignore recommendations if touches other type 2022-02-01 11:06:32 +01:00
Dominique Eiflaender e68c6798be Pull request #337: RED-3243: Improved imported redactions logic
Merge in RED/redaction-service from RED-3243-8 to master

* commit '58b1db66cc2cc67b3b4e26295478824e5052d6a3':
  RED-3243: Improved imported redactions logic
2022-01-31 14:47:54 +01:00
deiflaender 58b1db66cc RED-3243: Improved imported redactions logic 2022-01-31 14:42:49 +01:00
deiflaender 0072d38d68 Use base image to speedup build 2022-01-31 12:05:10 +01:00
deiflaender 25a409d3a5 RED-3243: Added ImportedRedaction model classes 2022-01-31 11:11:32 +01:00
Timo Bejan ca1da20ba3 Pull request #335: immutable field fix
Merge in RED/redaction-service from RED-3242 to master

* commit '3c691a1afc9ce67845aad5d15bbc99a0113f6a6b':
  immutable field fix
2022-01-28 10:57:04 +01:00
Timo Bejan 3c691a1afc immutable field fix 2022-01-28 11:29:43 +02:00
Timo Bejan 6813fbc3cb Pull request #333: since no more manual changes are fields of redactionLog, we can safely always merge, even legacy ones
Merge in RED/redaction-service from RED-3242 to master

* commit 'c9a75152fb9b4e02b3421dd16ae54056889b0aa9':
  since no more manual changes are fields of redactionLog, we can safely always merge, even legacy ones
2022-01-27 15:59:39 +01:00
Dominique Eiflaender a8a6fef07a Pull request #334: RED-3239: Protoyped logic for imported redactions
Merge in RED/redaction-service from RED-3239 to master

* commit '26661fa87beeb20b32f2be5a8b1b025e8d2d29fd':
  RED-3239: Protoyped logic for imported redactions
2022-01-27 15:31:16 +01:00
deiflaender 26661fa87b RED-3239: Protoyped logic for imported redactions 2022-01-27 15:21:33 +01:00
Timo Bejan c9a75152fb since no more manual changes are fields of redactionLog, we can safely always merge, even legacy ones 2022-01-27 15:30:43 +02:00
Timo Bejan 0da2a1ab26 Pull request #332: RED-3242 - excluded from automatic analysis - reworked redactionLog architecture
Merge in RED/redaction-service from RED-3242 to master

* commit 'd92a723fea21280d7bf482c3d74c7b177dbaa030':
  RED-3242 - excluded from automatic analysis - reworked redactionLog architecture
2022-01-27 14:06:35 +01:00
Timo Bejan d92a723fea RED-3242 - excluded from automatic analysis - reworked redactionLog architecture 2022-01-27 14:58:14 +02:00
Dominique Eiflaender 3109e3e4f0 Pull request #331: RED-3133: Ignore min length for values from ai
Merge in RED/redaction-service from RED-3133-Fix4 to master

* commit 'a1544588e8a3cfa0479d5d31d82463c5434674a1':
  RED-3133: Ignore min length for values from ai
2022-01-26 16:32:23 +01:00
deiflaender a1544588e8 RED-3133: Ignore min length for values from ai 2022-01-26 16:18:41 +01:00
Dominique Eiflaender 8ba2bd776d Pull request #330: RED-3133: Fixed wrong ai entries again
Merge in RED/redaction-service from RED-3133-Fix2 to master

* commit '8ac7c309d981c8899eca40872a49198284d15944':
  RED-3133: Fixed wrong ai entries again
2022-01-26 15:42:13 +01:00
deiflaender 8ac7c309d9 RED-3133: Fixed wrong ai entries again 2022-01-26 15:31:13 +01:00
Dominique Eiflaender b8f7be28e7 Pull request #329: RED-3133: Fixed wrong ai entries
Merge in RED/redaction-service from RED-3133-Fix to master

* commit '921ae1839cb424a4d82afd88401fddc9cf855f8c':
  RED-3133: Fixed wrong ai entries
2022-01-26 15:09:04 +01:00
deiflaender 921ae1839c RED-3133: Fixed wrong ai entries 2022-01-26 14:43:54 +01:00
Dominique Eiflaender 38a386bc87 Pull request #328: RED-3133: Fixed bug in address combination rule
Merge in RED/redaction-service from RED-3133-4 to master

* commit 'c21cf3dd5cdf70dfb838bb18f78a7c32715f8db8':
  RED-3133: Fixed bug in address combination rule
2022-01-26 13:09:13 +01:00
deiflaender c21cf3dd5c RED-3133: Fixed bug in address combination rule 2022-01-26 12:57:02 +01:00
Dominique Eiflaender 29ad27dea6 Pull request #326: RED-3243: Fixed bug in combining address parts
Merge in RED/redaction-service from RED-3243 to master

* commit '6a78d798912afd4d625455d62d2ec416a311873b':
  RED-3243: Fixed bug in combining address parts
2022-01-24 12:53:40 +01:00
deiflaender 6a78d79891 RED-3243: Fixed bug in combining address parts 2022-01-24 12:35:38 +01:00
Dominique Eiflaender 726c272e61 Pull request #325: Improved rule to combine AI matches
Merge in RED/redaction-service from RED-3133-2 to master

* commit 'f32d9a526787a6a74179a0e8f5589763a2080fca':
  Improved rule to combine AI matches
2022-01-21 13:15:39 +01:00
deiflaender f32d9a5267 Improved rule to combine AI matches 2022-01-21 12:53:33 +01:00
Dominique Eiflaender c4c96c1712 Pull request #324: RED-3133: Add and combine AI Entities in rules
Merge in RED/redaction-service from RED-3133 to master

* commit '6e720a15c2b9c9d46b52b94f63f0af157ac5d81a':
  RED-3133: Add and combine AI Entities in rules
2022-01-20 12:28:17 +01:00
deiflaender 6e720a15c2 RED-3133: Add and combine AI Entities in rules 2022-01-20 12:11:45 +01:00
Dominique Eiflaender 3687a7e5a9 Pull request #323: RED-3190: Ignore if surrounding text can not be calculate because of different parsing order from ui and backend
Merge in RED/redaction-service from RED-3190-master to master

* commit '6a31ae642d34c61865b55b6b138ef4406c27ab63':
  RED-3190: Ignore if surrounding text can not be calculate because of different parsing order from ui and backend
2022-01-14 12:10:42 +01:00
deiflaender 6a31ae642d RED-3190: Ignore if surrounding text can not be calculate because of different parsing order from ui and backend 2022-01-14 12:04:50 +01:00
Ali Oezyetimoglu e01d49754d Pull request #321: RED-3183: Upgrade log4j to 2.17.1
Merge in RED/redaction-service from RED-3183-rs1 to master

* commit '5de4702d5ac0c05ff23ef89396028be121474600':
  RED-3183: Upgrade log4j to 2.17.1
2022-01-13 15:38:28 +01:00
aoezyetimoglu 5de4702d5a RED-3183: Upgrade log4j to 2.17.1 2022-01-13 15:24:31 +01:00
Dominique Eiflaender c621eb9f04 Pull request #320: RED-3173: Fixed wrong skipped redaction afer remove and readd to dictionary
Merge in RED/redaction-service from RED-3173-master to master

* commit 'eb9fe0e3ddfc9b86cb3b0664e8bec5ef5b4d34c7':
  RED-3173: Fixed wrong skipped redaction afer remove and readd to dictionary
2022-01-13 11:20:56 +01:00
deiflaender eb9fe0e3dd RED-3173: Fixed wrong skipped redaction afer remove and readd to dictionary 2022-01-13 11:14:45 +01:00
Timo Bejan f05977b7ae Pull request #318: analysis version in redactionlog and changes
Merge in RED/redaction-service from analysis-number-in-redaction-log-m to master

* commit '1cad386db83a2d2057701989c0c179b770248f5e':
  analysis version in redactionlog and changes
2022-01-13 09:23:34 +01:00
Timo Bejan 1cad386db8 analysis version in redactionlog and changes 2022-01-13 10:08:34 +02:00
cschabert 21a8af5ca5 add build before sonar job 2022-01-12 13:32:47 +01:00
Dominique Eiflaender 9c9583067f Pull request #316: RED-2440: Integrated image-service-v2
Merge in RED/redaction-service from RED-2440 to master

* commit 'dee1aa1f01085e585bb3126b7883bd3d01090f17':
  RED-2440: Integrated image-service-v2
2021-12-22 10:40:04 +01:00
deiflaender dee1aa1f01 RED-2440: Integrated image-service-v2 2021-12-22 10:35:13 +01:00
Ali Oezyetimoglu db16f8c1da Pull request #314: RED-3127: Upgrade log4j to 2.17.0
Merge in RED/redaction-service from RED-3127-rs1 to master

* commit '6911c5cdd5a0e559f4abfbd083b29aca9fbae9d9':
  RED-3127: Upgrade log4j to 2.17.0
2021-12-21 12:17:40 +01:00
aoezyetimoglu 6911c5cdd5 RED-3127: Upgrade log4j to 2.17.0 2021-12-21 12:00:24 +01:00
Timo Bejan 37c3814ebd Pull request #313: condition fix
Merge in RED/redaction-service from redaction-log-merge-service-test to master

* commit 'e8ae8728f3abd82cacd641144406f9047ca9c262':
  condition fix
2021-12-21 11:07:37 +01:00
Timo Bejan e8ae8728f3 condition fix 2021-12-21 11:18:37 +02:00
Timo Bejan b0415adaf4 Pull request #312: Redaction log merge service test
Merge in RED/redaction-service from redaction-log-merge-service-test to master

* commit '9e64773d0db262481f8552678a6e6b86d81d93c4':
  pmd
  skip remove from dict entries from merge
2021-12-21 10:17:54 +01:00
Timo Bejan 9e64773d0d pmd 2021-12-21 11:08:15 +02:00
Timo Bejan 3145195d63 skip remove from dict entries from merge 2021-12-21 11:05:47 +02:00
Dominique Eiflaender 88b1274cd3 Pull request #311: RED-3117: Enbled to ignore invalid entries at addToDictionary for automated pushing entries in analysis
Merge in RED/redaction-service from RED-3117 to master

* commit '0296745efc0e4786386811d0037e2cae6ae511c4':
  RED-3117: Enbled to ignore invalid entries at addToDictionary for automated pushing entries in analysis
2021-12-20 11:58:34 +01:00
deiflaender 0296745efc RED-3117: Enbled to ignore invalid entries at addToDictionary for automated pushing entries in analysis 2021-12-20 11:53:36 +01:00
Dominique Eiflaender 60a0adb843 Pull request #310: RED-3114: Fixed unreliable redactions on rotated pages
Merge in RED/redaction-service from RED-3114 to master

* commit 'f1071de3a9ea9a3b34798f21ee9589262ef4f32b':
  RED-3114: Fixed unreliable redactions on rotated pages
2021-12-17 16:00:18 +01:00
deiflaender f1071de3a9 RED-3114: Fixed unreliable redactions on rotated pages 2021-12-17 15:55:09 +01:00
Ali Oezyetimoglu 681d17ad95 Pull request #309: RED-3116: Fix missing relativePath in mvn projects to leads to non working sync on lastest intellij
Merge in RED/redaction-service from RED-3116-rs1 to master

* commit '8deb24a5342c1c7a34fa6235e3d68f8161d884e4':
  RED-3116: Fix missing relativePath in mvn projects to leads to non working sync on lastest intellij
2021-12-17 13:53:14 +01:00
aoezyetimoglu 8deb24a534 RED-3116: Fix missing relativePath in mvn projects to leads to non working sync on lastest intellij 2021-12-17 13:10:43 +01:00
Ali Oezyetimoglu 98443f50ec Pull request #308: RED-3107 rs1
Merge in RED/redaction-service from RED-3107-rs1 to master

* commit '7d75961df45de93cfb2a708b640703dbd5693f40':
  RED-3107: Missing headline for tables appears as inconsistent text in report
  RED-3106: Upgrade log4j to 2.16.0
2021-12-17 12:46:53 +01:00
aoezyetimoglu 7d75961df4 RED-3107: Missing headline for tables appears as inconsistent text in report 2021-12-17 12:00:14 +01:00
aoezyetimoglu 3c1f4b4853 Merge branch 'master' of ssh://git.iqser.com:2222/red/redaction-service 2021-12-17 11:52:52 +01:00
Dominique Eiflaender c9e74f4ff9 Pull request #307: RED-3105: Fixed change computation
Merge in RED/redaction-service from RED-3105 to master

* commit '19dcf0a67434442b2dd578523bbc5efa49c644e8':
  RED-3105: Fixed change computation
2021-12-16 14:22:55 +01:00
deiflaender 19dcf0a674 RED-3105: Fixed change computation 2021-12-16 14:17:04 +01:00
Philipp Schramm c46c41b790 Pull request #306: RED-3067 Bugfix to show redactions on rotated pages correctly
Merge in RED/redaction-service from RED-3067 to master

* commit 'f49782438461ec34e6e8c0ccf88b3017755e593d':
  RED-3067 Bugfix to show redactions on rotated pages correctly and fixed getting tmp directory
  RED-3067 Bugfix to show redactions on rotated pages correctly
2021-12-16 14:14:40 +01:00
Philipp Schramm f497824384 RED-3067 Bugfix to show redactions on rotated pages correctly and fixed getting tmp directory 2021-12-16 14:08:05 +01:00
Philipp Schramm f7fbe8f0f6 RED-3067 Bugfix to show redactions on rotated pages correctly 2021-12-16 12:43:17 +01:00
Ali Oezyetimoglu e8dce509f3 Pull request #303: RED-3106: Upgrade log4j to 2.16.0
Merge in RED/redaction-service from RED-3106-rs1 to master

* commit '9346cc701e1601b6c9d44e41de9fc193fbc81b1e':
  RED-3106: Upgrade log4j to 2.16.0
2021-12-16 09:57:54 +01:00
aoezyetimoglu b125de2b55 RED-3106: Upgrade log4j to 2.16.0 2021-12-16 09:52:21 +01:00
aoezyetimoglu 9346cc701e RED-3106: Upgrade log4j to 2.16.0 2021-12-16 09:44:04 +01:00
Dominique Eiflaender 1987e7b430 Pull request #302: RED-3025: Ported code to ignore hints
Merge in RED/redaction-service from RED-3025 to master

* commit '010a3ee6ab7f06297c2c37691bcac2f7019fb3a0':
  RED-3025: Ported code to ignore hints
2021-12-15 13:26:48 +01:00
deiflaender 010a3ee6ab RED-3025: Ported code to ignore hints 2021-12-15 13:22:28 +01:00
Philipp Schramm 358f8814cd Pull request #300: RED-3032 Bugfix: Throw NotFoundException in case section grid was not found
Merge in RED/redaction-service from bugfix/RED-3032 to master

* commit '9fb4024fb7dfe7ae3f92f5c17decf15fbac58451':
  RED-3032 Bugfix: Throw NotFoundException in case section grid was not found
2021-12-15 09:05:10 +01:00
Philipp Schramm 9fb4024fb7 RED-3032 Bugfix: Throw NotFoundException in case section grid was not found 2021-12-14 17:02:24 +01:00
Ali Oezyetimoglu a155f18adc Pull request #299: RED-3061 rs3
Merge in RED/redaction-service from RED-3061-rs3 to master

* commit '1ccf0ab493fcb367a071641e2fea050cf6ad26cf':
  RED-3061: Logic for value matcher for initials expansion rule is inverted
  RED-3061: Logic for value matcher for initials expansion rule is inverted
2021-12-14 12:35:45 +01:00
aoezyetimoglu 1ccf0ab493 RED-3061: Logic for value matcher for initials expansion rule is inverted 2021-12-14 12:25:21 +01:00
aoezyetimoglu 1b9251b0a2 RED-3061: Logic for value matcher for initials expansion rule is inverted 2021-12-14 11:10:59 +01:00
Dominique Eiflaender 6bfa54583f Pull request #297: RED-2439: Fixed missing MessageType in AnalyseResult
Merge in RED/redaction-service from RED-2439-4 to master

* commit '83a757976adf26cfeae8fb682593ce1d5f606dfc':
  RED-2439: Fixed missing MessageType in AnalyseResult
2021-12-13 13:55:52 +01:00
deiflaender 83a757976a RED-2439: Fixed missing MessageType in AnalyseResult 2021-12-13 13:51:33 +01:00
Dominique Eiflaender bd5c997572 Pull request #296: RED-2439: Enabled to find surrounding text for manual redactions via queue
Merge in RED/redaction-service from RED-2439-3 to master

* commit 'e4cf3bfa602014cd8a2f2162199557880600e93f':
  RED-2439: Enabled to find surrounding text for manual redactions via queue
2021-12-13 12:32:20 +01:00
deiflaender e4cf3bfa60 RED-2439: Enabled to find surrounding text for manual redactions via queue 2021-12-13 12:24:26 +01:00
Ali Oezyetimoglu 3f153c1df5 Pull request #294: RED-3075: Upgraded log4j to 2.15.0
Merge in RED/redaction-service from RED-3075-rs2 to master

* commit '142c81d27d9e5e67532b3d8795c2d4c559342d37':
  RED-3075: Upgraded log4j to 2.15.0
2021-12-13 11:03:56 +01:00
aoezyetimoglu 142c81d27d RED-3075: Upgraded log4j to 2.15.0 2021-12-13 10:59:16 +01:00
Dominique Eiflaender a2c57b94a2 Pull request #292: RED-3059: Fixed inital expansion overlaps
Merge in RED/redaction-service from RED-3059-port to master

* commit '0e21e5b4d988d43040573a4fc71547829c815ec0':
  RED-3059: Fixed inital expansion overlaps
2021-12-10 11:37:24 +01:00
deiflaender 0e21e5b4d9 RED-3059: Fixed inital expansion overlaps 2021-12-10 11:32:53 +01:00
Dominique Eiflaender 29a66ee5cf Pull request #290: RED-2439: Added endpoint to calculate textBefore and textAfter for manual redactions
Merge in RED/redaction-service from RED-2439 to master

* commit '6950c00711dc113bb012a9cd3cc26c5c276409a0':
  RED-2439: Added endpoint to calculate textBefore and textAfter for manual redactions
2021-12-09 16:19:24 +01:00
deiflaender 6950c00711 RED-2439: Added endpoint to calculate textBefore and textAfter for manual redactions 2021-12-09 16:12:02 +01:00
Philipp Schramm fa4b2263bc Pull request #288: RED-2795 Bugfix with inconsistent types for recategorizing Images in specific cases
Merge in RED/redaction-service from bugfix/RED-2795 to master

* commit '415df2b3658b4525120a15fb0f341164a73bb3d4':
  RED-2795 Bugfix with inconsistent types for recategorizing Images in specific cases
2021-12-07 11:15:30 +01:00
Philipp Schramm 415df2b365 RED-2795 Bugfix with inconsistent types for recategorizing Images in specific cases 2021-12-07 11:10:53 +01:00
Philipp Schramm cda8e51d9d Pull request #286: Bugfix/RED-2845 3.0
Merge in RED/redaction-service from bugfix/RED-2845_3.0 to master

* commit '1c982a40422a16e9caa8d264e4221524c7e1c92b':
  RED-2845 Bugfix: Avoid the expansion if it would result in a redaction overlap and bugfix in RegExp in drools
2021-12-02 14:07:45 +01:00
Philipp Schramm 1c982a4042 RED-2845 Bugfix: Avoid the expansion if it would result in a redaction overlap and bugfix in RegExp in drools 2021-12-02 14:02:31 +01:00
Dominique Eiflaender 456bd7e093 Pull request #285: RED-2862: Added possibility to ignore dictionaries by rule
Merge in RED/redaction-service from RED-2862-3.0.0 to master

* commit 'f37f3b2dcf10102d03bbf3fb3233a801537d45ad':
  RED-2862: Added possibility to ignore dictionaries by rule
2021-12-01 12:46:10 +01:00
deiflaender f37f3b2dcf RED-2862: Added possibility to ignore dictionaries by rule 2021-12-01 12:40:59 +01:00
Dominique Eiflaender 809c563934 Pull request #283: Hotfix for duplicate entries in redactionLog
Merge in RED/redaction-service from DuplicateIdsMaster to master

* commit '46d14cfc6441588ee57e19d4780ffebcf70e3367':
  Hotfix for duplicate entries in redactionLog
2021-11-30 16:36:03 +01:00
deiflaender 46d14cfc64 Hotfix for duplicate entries in redactionLog 2021-11-30 16:27:59 +01:00
Dominique Eiflaender 459c977f7e Pull request #281: RED-2861: Ported changes for rectangle redaction improvements
Merge in RED/redaction-service from RED-2861-port to master

* commit '7b1c9c97e672d78e0fbf7f05138db853a11e7d04':
  RED-2861: Ported changes for rectangle redaction improvements
2021-11-30 12:52:26 +01:00
deiflaender 7b1c9c97e6 RED-2861: Ported changes for rectangle redaction improvements 2021-11-30 12:47:22 +01:00
Philipp Schramm 8d881d4ca6 Pull request #280: RED-2756 Bugfix with redactions are not continuous and added some entries to dictionary
Merge in RED/redaction-service from bugfix/RED-2756 to master

* commit '35a4d004fda0404e9b19e7112d25627cbde2a932':
  RED-2756 Bugfix with redactions are not continuous and added some entries to dictionary
2021-11-30 11:15:32 +01:00
Philipp Schramm 35a4d004fd RED-2756 Bugfix with redactions are not continuous and added some entries to dictionary 2021-11-30 11:05:06 +01:00
Ali Oezyetimoglu ad80d4247c Pull request #278: RED-2841: INC6207970 Rule for initials expansion should be applied only to dictionary entries without whitespaces
Merge in RED/redaction-service from RED-2841-rs1 to master

* commit 'd1317c5bd4f9522eecd78775656a0f7b5a74c3d7':
  RED-2841: INC6207970 Rule for initials expansion should be applied only to dictionary entries without whitespaces
2021-11-29 15:40:51 +01:00
aoezyetimoglu d1317c5bd4 RED-2841: INC6207970 Rule for initials expansion should be applied only to dictionary entries without whitespaces 2021-11-29 12:38:53 +01:00
Philipp Schramm f5817204bf Pull request #277: Bugfix/RED-2756
Merge in RED/redaction-service from bugfix/RED-2756 to master

* commit '9d0fafd63ddb23fd8ee2f154e02df88687e08f96':
  RED-2756 Bugfix with redactions are not continuous
  RED-2756 Bugfix for 'Redaction is not continuous', compare line height and y position instead of rounding y values
2021-11-29 10:36:05 +01:00
Philipp Schramm 9d0fafd63d RED-2756 Bugfix with redactions are not continuous 2021-11-29 10:31:32 +01:00
Philipp Schramm 6230f42fcf RED-2756 Bugfix for 'Redaction is not continuous', compare line height and y position instead of rounding y values 2021-11-25 16:40:26 +01:00
Timo Bejan 571035dda1 Pull request #275: redaction-log info for RED-2659
Merge in RED/redaction-service from section-info to master

* commit '892fa6ade0c9f260183686535612e7423ed2ec09':
  redaction-log info for RED-2659
2021-11-22 13:49:44 +01:00
Timo Bejan 892fa6ade0 redaction-log info for RED-2659 2021-11-22 14:45:02 +02:00
Timo Bejan ab9dcbbafb Pull request #274: section info port
Merge in RED/redaction-service from section-info to master

* commit '980d14b6049c0e7f52c2cc4fffbffa7d5bf54b08':
  section info port
2021-11-22 08:51:10 +01:00
Timo Bejan 980d14b604 section info port 2021-11-22 09:46:12 +02:00
Timo Bejan 1cefa3dc05 Pull request #273: 2 new falgs for UI indicators
Merge in RED/redaction-service from redaction-log-updates to master

* commit 'f80eb6d791f35aa96249ef783e56164db85c8278':
  2 new falgs for UI indicators
2021-11-18 10:24:39 +01:00
Timo Bejan f80eb6d791 2 new falgs for UI indicators 2021-11-18 11:18:34 +02:00
Philipp Schramm e42d5180ec Pull request #272: RED-2430 do not exclude manual entries from excluded pages
Merge in RED/redaction-service from feature/RED-2430 to master

* commit '3ae8b2cd666eee2fff10e2254cd829084e503893':
  RED-2430 do not exclude manual entries from excluded pages
2021-11-16 08:53:19 +01:00
Philipp Schramm 3ae8b2cd66 RED-2430 do not exclude manual entries from excluded pages 2021-11-15 16:39:53 +01:00
Philipp Schramm a6a1d51c17 Pull request #271: RED-2633 Added section to manual redactions, updated version from persistence-service and extracted versions in pom.xm
Merge in RED/redaction-service from RED-2633 to master

* commit '6e713c4fcc010e6461e403256c4926974cec2cf8':
  RED-2633 Added section to manual redactions, updated version from persistence-service and extracted versions in pom.xm
2021-11-15 14:56:52 +01:00
Philipp Schramm 6e713c4fcc RED-2633 Added section to manual redactions, updated version from persistence-service and extracted versions in pom.xm 2021-11-15 14:36:35 +01:00
Dominique Eiflaender 2c6957360b Pull request #269: RED-2742: Fixed annotation positions for rotation = 0 and direction = 90
Merge in RED/redaction-service from RED-2742 to master

* commit 'a91877769370c804b7d9d9fa4d4e9e16752598bd':
  RED-2742: Fixed annotation positions for rotation = 0 and direction = 90
2021-11-12 10:16:52 +01:00
deiflaender a918777693 RED-2742: Fixed annotation positions for rotation = 0 and direction = 90 2021-11-12 10:06:27 +01:00
Dominique Eiflaender d8662e8e20 Pull request #266: RED-2624: Fixed footnote is recognized as headline
Merge in RED/redaction-service from RED-2624 to master

* commit '3c861074c9923a59fc71e2f57ee47c838ddbe3fe':
  RED-2624: Fixed footnote is recognized as headline
2021-11-03 12:25:43 +01:00
Dominique Eifländer 3c861074c9 RED-2624: Fixed footnote is recognized as headline 2021-11-03 12:11:38 +01:00
Dominique Eiflaender 3250d1791c Pull request #265: RED-2623: Fixed headline handline on strange ocr document
Merge in RED/redaction-service from RED-2623 to master

* commit '1b328b2b68da0b5be71d2f7464c767357003e4f2':
  RED-2623: Fixed headline handline on strange ocr document
2021-11-02 16:35:06 +01:00
Dominique Eifländer 1b328b2b68 RED-2623: Fixed headline handline on strange ocr document 2021-11-02 14:47:41 +01:00
Christoph  Schabert affed2258c Pull request #261: Split nightly and normal build
Merge in RED/redaction-service from RED-2312 to master

* commit '499c36b53d9b307c4899774e1d669482aa850a9f':
  Split nightly and normal build
2021-11-02 11:13:47 +01:00
Dominique Eiflaender 9d0973c037 Pull request #262: RED-2482: Always redact manual justification changes
Merge in RED/redaction-service from RED-2482-A to master

* commit '394e2fd4ed24e765e1bfc919688107a96af85fec':
  RED-2482: Always redact manual justification changes
2021-10-28 15:18:59 +02:00
Dominique Eifländer 394e2fd4ed RED-2482: Always redact manual justification changes 2021-10-28 15:09:34 +02:00
cschabert 499c36b53d Split nightly and normal build 2021-10-28 15:00:46 +02:00
Dominique Eiflaender 65b186be28 Pull request #259: RED-2536
Merge in RED/redaction-service from RED-2536 to master

* commit 'd892d6e81ee12927e57dc46c6439a65276824896':
  RED-2536: Treat \t same as whitespace
  RED-2223: Fixed stange end of textposition sequences that leads to wrong whitespaces
2021-10-28 12:52:47 +02:00
Dominique Eifländer d892d6e81e RED-2536: Treat \t same as whitespace 2021-10-28 12:42:18 +02:00
Dominique Eifländer 50017e7230 RED-2223: Fixed stange end of textposition sequences that leads to wrong whitespaces 2021-10-28 12:23:58 +02:00
Dominique Eiflaender 997317a683 Pull request #258: RED-2575: Fixed annotating entries on pages with cropbox != mediabox
Merge in RED/redaction-service from RED-2575 to master

* commit '012c5a794639eee34e392e940d5e380a5ea06df7':
  RED-2575: Fixed annotating entries on pages with cropbox != mediabox
2021-10-26 14:23:32 +02:00
Dominique Eifländer 012c5a7946 RED-2575: Fixed annotating entries on pages with cropbox != mediabox 2021-10-26 13:51:47 +02:00
Ali Oezyetimoglu a351761257 Pull request #256: RED-2429: As a user I want to resize a redaction
Merge in RED/redaction-service from RED-2429-rs1 to master

* commit 'd8f8702b2a78e7a3bc12e55791c251f3445d5b5e':
  RED-2429: As a user I want to resize a redaction
2021-10-22 12:23:54 +02:00
aoezyetimoglu d8f8702b2a RED-2429: As a user I want to resize a redaction 2021-10-22 12:05:31 +02:00
Dominique Eiflaender 0abcca97b3 Pull request #255: RED-2418: Fixed not working exception handling if redactionlog is not present
Merge in RED/redaction-service from RED-2418 to master

* commit 'f66a054cdea311376ffa88a6641b7eae9be6689f':
  RED-2418: Fixed not working exception handling if redactionlog is not present
2021-10-22 11:05:38 +02:00
Dominique Eifländer f66a054cde RED-2418: Fixed not working exception handling if redactionlog is not present 2021-10-22 10:33:42 +02:00
Dominique Eiflaender 0cc71897b8 Pull request #254: RED-2224: Fixed overriding entries with higher rank
Merge in RED/redaction-service from RED-2224-2 to master

* commit '634eee17167e01e2d4ee4d3d7b9613225e8c5643':
  RED-2224: Fixed overriding entries with higher rank
2021-10-19 15:38:44 +02:00
Dominique Eifländer 634eee1716 RED-2224: Fixed overriding entries with higher rank 2021-10-19 15:32:20 +02:00
Dominique Eiflaender fbad9a759b Pull request #253: RED-2224: Fixed missing values in engines
Merge in RED/redaction-service from RED-2224 to master

* commit 'fa8f598763efb334e21624df468d7e1802477076':
  RED-2224: Fixed missing values in engines
2021-10-19 11:45:38 +02:00
Dominique Eifländer fa8f598763 RED-2224: Fixed missing values in engines 2021-10-19 10:55:51 +02:00
Dominique Eiflaender 46c6385d11 Pull request #250: RED-2223: Fixed adding annotations to rotated words on non rotated pages
Merge in RED/redaction-service from RED-2223 to master

* commit '6005c476962dcdfd1d4e4fabf10fa840377b78db':
  RED-2223: Fixed adding annotations to rotated words on non rotated pages
2021-10-15 15:35:28 +02:00
Dominique Eifländer 6005c47696 RED-2223: Fixed adding annotations to rotated words on non rotated pages 2021-10-15 15:27:15 +02:00
Dominique Eiflaender ac556dfc9f Pull request #249: RED-2468: Fixed push recommendations to dictionary
Merge in RED/redaction-service from RED-2468 to master

* commit '15d836644b6a74273e81de361091d6ea1b71bea9':
  RED-2468: Fixed push recommendations to dictionary
2021-10-15 10:45:10 +02:00
Dominique Eifländer 15d836644b RED-2468: Fixed push recommendations to dictionary 2021-10-15 10:36:01 +02:00
326 changed files with 122795 additions and 2812 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>com.atlassian.bamboo</groupId>
<artifactId>bamboo-specs-parent</artifactId>
<version>7.2.2</version>
<version>8.1.3</version>
<relativePath/>
</parent>
@@ -1,7 +1,12 @@
package buildjob;
import static com.atlassian.bamboo.specs.builders.task.TestParserTask.createJUnitParserTask;
import java.time.LocalTime;
import com.atlassian.bamboo.specs.api.BambooSpec;
import com.atlassian.bamboo.specs.api.builders.BambooKey;
import com.atlassian.bamboo.specs.api.builders.Variable;
import com.atlassian.bamboo.specs.api.builders.docker.DockerConfiguration;
import com.atlassian.bamboo.specs.api.builders.permission.PermissionType;
import com.atlassian.bamboo.specs.api.builders.permission.Permissions;
@@ -14,17 +19,16 @@ import com.atlassian.bamboo.specs.api.builders.plan.branches.BranchCleanup;
import com.atlassian.bamboo.specs.api.builders.plan.branches.PlanBranchManagement;
import com.atlassian.bamboo.specs.api.builders.project.Project;
import com.atlassian.bamboo.specs.builders.task.CheckoutItem;
import com.atlassian.bamboo.specs.builders.task.CleanWorkingDirectoryTask;
import com.atlassian.bamboo.specs.builders.task.InjectVariablesTask;
import com.atlassian.bamboo.specs.builders.task.ScriptTask;
import com.atlassian.bamboo.specs.builders.task.VcsCheckoutTask;
import com.atlassian.bamboo.specs.builders.task.VcsTagTask;
import com.atlassian.bamboo.specs.builders.trigger.BitbucketServerTrigger;
import com.atlassian.bamboo.specs.builders.trigger.ScheduledTrigger;
import com.atlassian.bamboo.specs.model.task.InjectVariablesScope;
import com.atlassian.bamboo.specs.util.BambooServer;
import com.atlassian.bamboo.specs.builders.task.ScriptTask;
import com.atlassian.bamboo.specs.model.task.ScriptTaskProperties.Location;
import static com.atlassian.bamboo.specs.builders.task.TestParserTask.createJUnitParserTask;
import com.atlassian.bamboo.specs.util.BambooServer;
/**
* Plan configuration for Bamboo.
@@ -35,14 +39,15 @@ public class PlanSpec {
private static final String SERVICE_NAME = "redaction-service";
private static final String JVM_ARGS =" -Xmx4g -XX:+ExitOnOutOfMemoryError -XX:SurvivorRatio=2 -XX:NewRatio=1 -XX:InitialTenuringThreshold=16 -XX:MaxTenuringThreshold=16 -XX:InitiatingHeapOccupancyPercent=35 ";
private static final String JVM_ARGS = " -Xmx4g -XX:+ExitOnOutOfMemoryError -XX:SurvivorRatio=2 -XX:NewRatio=1 -XX:InitialTenuringThreshold=16 -XX:MaxTenuringThreshold=16 -XX:InitiatingHeapOccupancyPercent=35 ";
private static final String SERVICE_KEY = SERVICE_NAME.toUpperCase().replaceAll("-", "");
/**
* Run main to publish plan on Bamboo
*/
public static void main(final String[] args) throws Exception {
public static void main(final String[] args) {
//By default credentials are read from the '.credentials' file.
BambooServer bambooServer = new BambooServer("http://localhost:8085");
@@ -50,11 +55,22 @@ public class PlanSpec {
bambooServer.publish(plan);
PlanPermissions planPermission = new PlanSpec().createPlanPermission(plan.getIdentifier());
bambooServer.publish(planPermission);
Plan nightPlan = new PlanSpec().createNightPlan();
bambooServer.publish(nightPlan);
PlanPermissions nightPlanPermission = new PlanSpec().createPlanPermission(nightPlan.getIdentifier());
bambooServer.publish(nightPlanPermission);
Plan secPlan = new PlanSpec().createSecBuild();
bambooServer.publish(secPlan);
PlanPermissions secPlanPermission = new PlanSpec().createPlanPermission(secPlan.getIdentifier());
bambooServer.publish(secPlanPermission);
}
private PlanPermissions createPlanPermission(PlanIdentifier planIdentifier) {
Permissions permission = new Permissions()
.userPermissions("atlbamboo", PermissionType.EDIT, PermissionType.VIEW, PermissionType.ADMIN, PermissionType.CLONE, PermissionType.BUILD)
Permissions permission = new Permissions().userPermissions("atlbamboo", PermissionType.EDIT, PermissionType.VIEW, PermissionType.ADMIN, PermissionType.CLONE, PermissionType.BUILD)
.groupPermissions("development", PermissionType.EDIT, PermissionType.VIEW, PermissionType.CLONE, PermissionType.BUILD)
.groupPermissions("devplant", PermissionType.EDIT, PermissionType.VIEW, PermissionType.CLONE, PermissionType.BUILD)
.loggedInUserPermissions(PermissionType.VIEW)
@@ -62,67 +78,80 @@ public class PlanSpec {
return new PlanPermissions(planIdentifier.getProjectKey(), planIdentifier.getPlanKey()).permissions(permission);
}
private Project project() {
return new Project()
.name("RED")
.key(new BambooKey("RED"));
return new Project().name("RED").key(new BambooKey("RED"));
}
public Plan createPlan() {
return new Plan(
project(),
SERVICE_NAME, new BambooKey(SERVICE_KEY))
.description("Plan created from (enter repository url of your plan)")
.stages(new Stage("Default Stage")
.jobs(new Job("Default Job",
new BambooKey("JOB1"))
.tasks(
new ScriptTask()
.description("Clean")
.inlineBody("#!/bin/bash\n" +
"set -e\n" +
"rm -rf ./*"),
new VcsCheckoutTask()
.description("Checkout Default Repository")
.checkoutItems(new CheckoutItem().defaultRepository()),
new ScriptTask()
.description("Build")
.location(Location.FILE)
.fileFromPath("bamboo-specs/src/main/resources/scripts/build-java.sh")
.argument(SERVICE_NAME),
createJUnitParserTask()
.description("Resultparser")
.resultDirectories("**/test-reports/*.xml, **/target/surefire-reports/*.xml, **/target/failsafe-reports/*.xml")
.enabled(true),
new ScriptTask()
.description("Sonar")
.location(Location.FILE)
.fileFromPath("bamboo-specs/src/main/resources/scripts/sonar-java.sh")
.argument(SERVICE_NAME),
new InjectVariablesTask()
.description("Inject git Tag")
.path("git.tag")
.namespace("g")
.scope(InjectVariablesScope.LOCAL),
new VcsTagTask()
.description("${bamboo.g.gitTag}")
.tagName("${bamboo.g.gitTag}")
.defaultRepository())
.dockerConfiguration(
new DockerConfiguration()
.image("nexus.iqser.com:5001/infra/maven:3.6.2-jdk-13-3.0.0")
.volume("/etc/maven/settings.xml", "/usr/share/maven/ref/settings.xml")
.volume("/var/run/docker.sock", "/var/run/docker.sock")
)
)
)
return new Plan(project(), SERVICE_NAME, new BambooKey(SERVICE_KEY)).description("Plan created from (enter repository url of your plan)")
.variables(new Variable("maven_add_param", ""))
.stages(new Stage("Default Stage").jobs(new Job("Default Job", new BambooKey("JOB1")).tasks(new ScriptTask().description("Clean")
.inlineBody("#!/bin/bash\n" + "set -e\n" + "rm -rf ./*"), new VcsCheckoutTask().description("Checkout Default Repository")
.cleanCheckout(true)
.checkoutItems(new CheckoutItem().defaultRepository()), new ScriptTask().description("Build")
.location(Location.FILE)
.fileFromPath("bamboo-specs/src/main/resources/scripts/build-java.sh")
.argument(SERVICE_NAME), createJUnitParserTask().description("Resultparser")
.resultDirectories("**/test-reports/*.xml, **/target/surefire-reports/*.xml, **/target/failsafe-reports/*.xml")
.enabled(true), new InjectVariablesTask().description("Inject git Tag")
.path("git.tag")
.namespace("g")
.scope(InjectVariablesScope.LOCAL), new VcsTagTask().description("${bamboo.g.gitTag}").tagName("${bamboo.g.gitTag}").defaultRepository())
.dockerConfiguration(new DockerConfiguration().image("nexus.iqser.com:5001/infra/maven:3.8.4-openjdk-17-slim")
.volume("/etc/maven/settings.xml", "/usr/share/maven/ref/settings.xml")
.volume("/var/run/docker.sock", "/var/run/docker.sock"))))
.linkedRepositories("RED / " + SERVICE_NAME)
.triggers(new BitbucketServerTrigger())
.planBranchManagement(new PlanBranchManagement()
.createForVcsBranch()
.delete(new BranchCleanup()
.whenInactiveInRepositoryAfterDays(14))
.planBranchManagement(new PlanBranchManagement().createForVcsBranch()
.delete(new BranchCleanup().whenInactiveInRepositoryAfterDays(14))
.notificationForCommitters());
}
public Plan createNightPlan() {
return new Plan(project(), SERVICE_NAME + "-Night", new BambooKey(SERVICE_KEY + "NIGHT")).description("Long running nightly Plan for tests")
.variables(new Variable("maven_add_param", "-Dtest-groups=rules-test"))
.stages(new Stage("Default Stage").jobs(new Job("Default Job", new BambooKey("JOB1")).tasks(new CleanWorkingDirectoryTask().description("Clean working directory.")
.enabled(true), new VcsCheckoutTask().description("Checkout Default Repository")
.cleanCheckout(true)
.checkoutItems(new CheckoutItem().defaultRepository()), new ScriptTask().description("Build")
.location(Location.FILE)
.fileFromPath("bamboo-specs/src/main/resources/scripts/build-java.sh")
.argument(SERVICE_NAME), createJUnitParserTask().description("Resultparser")
.resultDirectories("**/test-reports/*.xml, **/target/surefire-reports/*.xml, **/target/failsafe-reports/*.xml")
.enabled(true))
.dockerConfiguration(new DockerConfiguration().image("nexus.iqser.com:5001/infra/maven:3.8.4-openjdk-17-slim")
.volume("/etc/maven/settings.xml", "/usr/share/maven/ref/settings.xml")
.volume("/var/run/docker.sock", "/var/run/docker.sock"))))
.linkedRepositories("RED / " + SERVICE_NAME)
.triggers(new ScheduledTrigger().scheduleOnceDaily(LocalTime.of(23, 00)))
.planBranchManagement(new PlanBranchManagement().delete(new BranchCleanup().whenInactiveInRepositoryAfterDays(14)).notificationForCommitters());
}
public Plan createSecBuild() {
return new Plan(project(), SERVICE_NAME + "-Sec", new BambooKey(SERVICE_KEY + "SEC")).description("Security Analysis Plan")
.stages(new Stage("Default Stage").jobs(new Job("Default Job", new BambooKey("JOB1")).tasks(new ScriptTask().description("Clean")
.inlineBody("#!/bin/bash\n" + "set -e\n" + "rm -rf ./*"), new VcsCheckoutTask().description("Checkout Default Repository")
.cleanCheckout(true)
.checkoutItems(new CheckoutItem().defaultRepository()), new ScriptTask().description("Sonar")
.location(Location.FILE)
.fileFromPath("bamboo-specs/src/main/resources/scripts/sonar-java.sh")
.argument(SERVICE_NAME))
.dockerConfiguration(new DockerConfiguration().image("nexus.iqser.com:5001/infra/maven:3.8.4-openjdk-17-slim")
.dockerRunArguments("--net=host")
.volume("/etc/maven/settings.xml", "/usr/share/maven/conf/settings.xml")
.volume("/var/run/docker.sock", "/var/run/docker.sock"))))
.linkedRepositories("RED / " + SERVICE_NAME)
.triggers(new ScheduledTrigger().scheduleOnceDaily(LocalTime.of(23, 00)))
.planBranchManagement(new PlanBranchManagement().createForVcsBranchMatching("release.*").notificationForCommitters());
}
}
@@ -3,49 +3,62 @@ set -e
SERVICE_NAME=$1
if [[ "${bamboo_version_tag}" = "dev" ]]
then
${bamboo_capability_system_builder_mvn3_Maven_3}/bin/mvn \
-f ${bamboo_build_working_directory}/$SERVICE_NAME-v1/pom.xml \
if [[ "$bamboo_planRepository_branchName" == "master" ]]
then
branchVersion=$(cat pom.xml | grep -Eo " <version>.*-SNAPSHOT</version>" | sed -s 's|<version>\(.*\)\..*\(-*.*\)</version>|\1|' | tr -d ' ')
latestVersion=$( semver $(git tag -l "${branchVersion}.*" ) | tail -n1 )
newVersion="$(semver $latestVersion -p -i minor)"
echo "new release on master with version $newVersion"
elif [[ "$bamboo_planRepository_branchName" == release* ]]
then
branchVersion=$(echo $bamboo_planRepository_branchName | sed -s 's|release\/\([0-9]\+\.[0-9]\+\)\.x|\1|')
latestVersion=$( semver $(git tag -l "${branchVersion}.*" ) | tail -n1 )
newVersion="$(semver $latestVersion -p -i patch)"
echo "new release on $bamboo_planRepository_branchName with version $newVersion"
elif [[ "${bamboo_version_tag}" != "dev" ]]
then
newVersion="${bamboo_version_tag}"
echo "new special version bild with $newVersion"
else
mvn -f ${bamboo_build_working_directory}/$SERVICE_NAME-v1/pom.xml \
--no-transfer-progress \
${bamboo_maven_add_param} \
clean install \
-Djava.security.egd=file:/dev/./urandomelse
else
${bamboo_capability_system_builder_mvn3_Maven_3}/bin/mvn \
--no-transfer-progress \
-f ${bamboo_build_working_directory}/$SERVICE_NAME-v1/pom.xml \
versions:set \
-DnewVersion=${bamboo_version_tag}
${bamboo_capability_system_builder_mvn3_Maven_3}/bin/mvn \
--no-transfer-progress \
-f ${bamboo_build_working_directory}/$SERVICE_NAME-image-v1/pom.xml \
versions:set \
-DnewVersion=${bamboo_version_tag}
${bamboo_capability_system_builder_mvn3_Maven_3}/bin/mvn \
-f ${bamboo_build_working_directory}/$SERVICE_NAME-v1/pom.xml \
--no-transfer-progress \
clean deploy \
-e \
-DdeployAtEnd=true \
-Dmaven.wagon.http.ssl.insecure=true \
-Dmaven.wagon.http.ssl.allowall=true \
-Dmaven.wagon.http.ssl.ignore.validity.dates=true \
-DaltDeploymentRepository=iqser_release::default::https://nexus.iqser.com/repository/red-platform-releases
echo "dev build with tag ${bamboo_planRepository_1_branch}_${bamboo_buildNumber}"
echo "gitTag=${bamboo_planRepository_1_branch}_${bamboo_buildNumber}" > git.tag
exit 0
fi
${bamboo_capability_system_builder_mvn3_Maven_3}/bin/mvn \
echo "gitTag=${newVersion}" > git.tag
mvn --no-transfer-progress \
-f ${bamboo_build_working_directory}/$SERVICE_NAME-v1/pom.xml \
${bamboo_maven_add_param} \
versions:set \
-DnewVersion=${newVersion}
mvn --no-transfer-progress \
-f ${bamboo_build_working_directory}/$SERVICE_NAME-image-v1/pom.xml \
${bamboo_maven_add_param} \
versions:set \
-DnewVersion=${newVersion}
mvn -f ${bamboo_build_working_directory}/$SERVICE_NAME-v1/pom.xml \
--no-transfer-progress \
clean deploy \
${bamboo_maven_add_param} \
-e \
-DdeployAtEnd=true \
-Dmaven.wagon.http.ssl.insecure=true \
-Dmaven.wagon.http.ssl.allowall=true \
-Dmaven.wagon.http.ssl.ignore.validity.dates=true \
-DaltDeploymentRepository=iqser_release::default::https://nexus.iqser.com/repository/red-platform-releases
mvn --no-transfer-progress \
-f ${bamboo_build_working_directory}/$SERVICE_NAME-image-v1/pom.xml \
package
${bamboo_capability_system_builder_mvn3_Maven_3}/bin/mvn \
--no-transfer-progress \
mvn --no-transfer-progress \
-f ${bamboo_build_working_directory}/$SERVICE_NAME-image-v1/pom.xml \
docker:push
if [[ "${bamboo_version_tag}" = "dev" ]]
then
echo "gitTag=${bamboo_planRepository_1_branch}_${bamboo_buildNumber}" > git.tag
else
echo "gitTag=${bamboo_version_tag}" > git.tag
fi
@@ -3,40 +3,42 @@ set -e
SERVICE_NAME=$1
echo "dependency-check:aggregate"
${bamboo_capability_system_builder_mvn3_Maven_3}/bin/mvn \
echo "build jar binaries"
mvn -f ${bamboo_build_working_directory}/$SERVICE_NAME-v1/pom.xml \
--no-transfer-progress \
clean install \
-Djava.security.egd=file:/dev/./urandomelse
echo "dependency-check:aggregate"
mvn --no-transfer-progress \
-f ${bamboo_build_working_directory}/$SERVICE_NAME-v1/pom.xml \
org.owasp:dependency-check-maven:aggregate
if [[ -z "${bamboo_repository_pr_key}" ]]
then
echo "Sonar Scan for branch: ${bamboo_planRepository_1_branch}"
${bamboo_capability_system_builder_mvn3_Maven_3}/bin/mvn \
--no-transfer-progress \
-f ${bamboo_build_working_directory}/$SERVICE_NAME-v1/pom.xml \
sonar:sonar \
-Dsonar.projectKey=RED_$SERVICE_NAME \
-Dsonar.host.url=https://sonarqube.iqser.com \
-Dsonar.login=${bamboo_sonarqube_api_token_secret} \
-Dsonar.branch.name=${bamboo_planRepository_1_branch} \
-Dsonar.dependencyCheck.jsonReportPath=target/dependency-check-report.json \
-Dsonar.dependencyCheck.xmlReportPath=target/dependency-check-report.xml \
-Dsonar.dependencyCheck.htmlReportPath=target/dependency-check-report.html
mvn --no-transfer-progress \
-f ${bamboo_build_working_directory}/$SERVICE_NAME-v1/pom.xml \
sonar:sonar \
-Dsonar.projectKey=RED_$SERVICE_NAME \
-Dsonar.host.url=https://sonarqube.iqser.com \
-Dsonar.login=${bamboo_sonarqube_api_token_secret} \
-Dsonar.branch.name=${bamboo_planRepository_1_branch} \
-Dsonar.dependencyCheck.jsonReportPath=target/dependency-check-report.json \
-Dsonar.dependencyCheck.xmlReportPath=target/dependency-check-report.xml \
-Dsonar.dependencyCheck.htmlReportPath=target/dependency-check-report.html
else
echo "Sonar Scan for PR with key1: ${bamboo_repository_pr_key}"
${bamboo_capability_system_builder_mvn3_Maven_3}/bin/mvn \
--no-transfer-progress \
-f ${bamboo_build_working_directory}/$SERVICE_NAME-v1/pom.xml \
sonar:sonar \
-Dsonar.projectKey=RED_$SERVICE_NAME \
-Dsonar.host.url=https://sonarqube.iqser.com \
-Dsonar.login=${bamboo_sonarqube_api_token_secret} \
-Dsonar.pullrequest.key=${bamboo_repository_pr_key} \
-Dsonar.pullrequest.branch=${bamboo_repository_pr_sourceBranch} \
-Dsonar.pullrequest.base=${bamboo_repository_pr_targetBranch} \
-Dsonar.dependencyCheck.jsonReportPath=target/dependency-check-report.json \
-Dsonar.dependencyCheck.xmlReportPath=target/dependency-check-report.xml \
-Dsonar.dependencyCheck.htmlReportPath=target/dependency-check-report.html
fi
mvn --no-transfer-progress \
-f ${bamboo_build_working_directory}/$SERVICE_NAME-v1/pom.xml \
sonar:sonar \
-Dsonar.projectKey=RED_$SERVICE_NAME \
-Dsonar.host.url=https://sonarqube.iqser.com \
-Dsonar.login=${bamboo_sonarqube_api_token_secret} \
-Dsonar.pullrequest.key=${bamboo_repository_pr_key} \
-Dsonar.pullrequest.branch=${bamboo_repository_pr_sourceBranch} \
-Dsonar.pullrequest.base=${bamboo_repository_pr_targetBranch} \
-Dsonar.dependencyCheck.jsonReportPath=target/dependency-check-report.json \
-Dsonar.dependencyCheck.xmlReportPath=target/dependency-check-report.xml \
-Dsonar.dependencyCheck.htmlReportPath=target/dependency-check-report.html
fi
@@ -10,8 +10,14 @@ import com.atlassian.bamboo.specs.api.util.EntityPropertiesBuilders;
public class PlanSpecTest {
@Test
public void checkYourPlanOffline() throws PropertiesValidationException {
Plan plan = new PlanSpec().createPlan();
Plan plan = new PlanSpec().createPlan();
EntityPropertiesBuilders.build(plan);
Plan nightPlan = new PlanSpec().createNightPlan();
EntityPropertiesBuilders.build(nightPlan);
Plan secPlan = new PlanSpec().createSecBuild();
EntityPropertiesBuilders.build(secPlan);
}
}
+2 -2
View File
@@ -7,7 +7,7 @@
<artifactId>redaction-service</artifactId>
<groupId>com.iqser.red.service</groupId>
<version>1.0-SNAPSHOT</version>
<version>3.0-SNAPSHOT</version>
<packaging>pom</packaging>
@@ -18,4 +18,4 @@
<module>redaction-service-image-v1</module>
</modules>
</project>
</project>
+4 -3
View File
@@ -5,7 +5,8 @@
<parent>
<groupId>com.iqser.red</groupId>
<artifactId>platform-docker-dependency</artifactId>
<version>1.1.0</version>
<version>1.2.0</version>
<relativePath />
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -41,7 +42,7 @@
<artifactId>docker-maven-plugin</artifactId>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
@@ -94,4 +95,4 @@
</plugins>
</pluginManagement>
</build>
</project>
</project>
@@ -1,4 +1,4 @@
FROM red/base-image:1.0.0
FROM red/redaction-service-base-v1:1.0.0
ARG PLATFORM_JAR
@@ -7,12 +7,3 @@ ENV PLATFORM_JAR ${PLATFORM_JAR}
ENV USES_ELASTICSEARCH false
COPY ["${PLATFORM_JAR}", "/"]
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
wget cabextract xfonts-utils fonts-liberation \
&& rm -rf /var/lib/apt/lists/*
RUN curl http://ftp.debian.org/debian/pool/contrib/m/msttcorefonts/ttf-mscorefonts-installer_3.8_all.deb -o /tmp/ttf-mscorefonts-installer_3.8_all.deb \
&& dpkg -i /tmp/ttf-mscorefonts-installer_3.8_all.deb \
&& rm /tmp/ttf-mscorefonts-installer_3.8_all.deb \
+5 -32
View File
@@ -5,7 +5,8 @@
<parent>
<artifactId>platform-dependency</artifactId>
<groupId>com.iqser.red</groupId>
<version>1.1.5</version>
<version>1.10.0</version>
<relativePath />
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -13,7 +14,6 @@
<groupId>com.iqser.red.service</groupId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
@@ -23,20 +23,19 @@
<properties>
<pdfbox.version>2.0.24</pdfbox.version>
<dsljson.version>1.9.9</dsljson.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.iqser.red</groupId>
<artifactId>platform-commons-dependency</artifactId>
<version>1.7.0</version>
<version>1.17.0</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
@@ -47,9 +46,7 @@
<artifactId>pdfbox-tools</artifactId>
<version>${pdfbox.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
@@ -58,12 +55,10 @@
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.9.0.2155</version>
</plugin>
</plugin>
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>6.3.1</version>
<configuration>
<format>ALL</format>
</configuration>
@@ -88,27 +83,5 @@
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.7</version>
<executions>
<execution>
<id>prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<goals>
<goal>report-aggregate</goal>
</goals>
<phase>verify</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -11,16 +11,51 @@
<artifactId>redaction-service-api-v1</artifactId>
<properties>
<persistence-service.version>1.240.0</persistence-service.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.dslplatform/dsl-json-java8 -->
<dependency>
<groupId>com.dslplatform</groupId>
<artifactId>dsl-json-java8</artifactId>
<version>${dsljson.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.iqser.red.service</groupId>
<artifactId>persistence-service-api-v1</artifactId>
<version>0.44.0</version>
<version>${persistence-service.version}</version>
<exclusions>
<exclusion>
<groupId>com.iqser.red.service</groupId>
<artifactId>redaction-service-api-v1</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessors>
<annotationProcessor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor</annotationProcessor>
<annotationProcessor>com.dslplatform.json.processor.CompiledJsonAnnotationProcessor</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -12,19 +12,20 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AnalyzeRequest {
private MessageType messageType;
private String dossierId;
private String fileId;
private String dossierTemplateId;
private boolean reanalyseOnlyIfPossible;
private ManualRedactions manualRedactions;
private OffsetDateTime lastProcessed;
private int analysisNumber;
@Builder.Default
private Set<Integer> excludedPages = new HashSet<>();
@@ -1,5 +1,6 @@
package com.iqser.red.service.redaction.v1.model;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.ManualRedactions;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -11,6 +12,8 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor
public class AnalyzeResult {
private MessageType messageType;
private String dossierId;
private String fileId;
private long duration;
@@ -24,6 +27,9 @@ public class AnalyzeResult {
private boolean wasReanalyzed;
private int analysisVersion;
private int analysisNumber;
private ManualRedactions manualRedactions;
}
@@ -1,18 +1,19 @@
package com.iqser.red.service.redaction.v1.model;
import java.time.OffsetDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.OffsetDateTime;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Change {
private int analysisNumber;
private ChangeType type;
private OffsetDateTime dateTime;
}
@@ -0,0 +1,21 @@
package com.iqser.red.service.redaction.v1.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ImportedRedaction {
private String id;
@Builder.Default
private List<Rectangle> positions = new ArrayList<>();
}
@@ -0,0 +1,22 @@
package com.iqser.red.service.redaction.v1.model;
import com.dslplatform.json.CompiledJson;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
@Builder
@CompiledJson
@NoArgsConstructor
@AllArgsConstructor
public class ImportedRedactions {
@Builder.Default
private Map<Integer, List<ImportedRedaction>> importedRedactions = new HashMap<>();
}
@@ -0,0 +1,50 @@
package com.iqser.red.service.redaction.v1.model;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.AnnotationStatus;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.BaseAnnotation;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.Map;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ManualChange {
private AnnotationStatus annotationStatus;
private ManualRedactionType manualRedactionType;
private OffsetDateTime processedDate;
private OffsetDateTime requestedDate;
private String userId;
private Map<String, String> propertyChanges = new HashMap<>();
public static ManualChange from(BaseAnnotation baseAnnotation) {
ManualChange manualChange = new ManualChange();
manualChange.annotationStatus = baseAnnotation.getStatus();
manualChange.processedDate = baseAnnotation.getProcessedDate();
manualChange.requestedDate = baseAnnotation.getRequestDate();
manualChange.userId = baseAnnotation.getUser();
return manualChange;
}
public boolean isProcessed() {
return processedDate != null;
}
public ManualChange withManualRedactionType(ManualRedactionType manualRedactionType) {
this.manualRedactionType = manualRedactionType;
return this;
}
public ManualChange withChange(String property, String value) {
this.propertyChanges.put(property, value);
return this;
}
}
@@ -1,5 +1,13 @@
package com.iqser.red.service.redaction.v1.model;
public enum ManualRedactionType {
ADD, REMOVE, FORCE_REDACT, RECATEGORIZE, LEGAL_BASIS_CHANGE
ADD_LOCALLY,
ADD_TO_DICTIONARY,
REMOVE_LOCALLY,
REMOVE_FROM_DICTIONARY,
FORCE_REDACT,
FORCE_HINT,
RECATEGORIZE,
LEGAL_BASIS_CHANGE,
RESIZE
}
@@ -0,0 +1,7 @@
package com.iqser.red.service.redaction.v1.model;
public enum MessageType {
ANALYSE, REANALYSE, STRUCTURE_ANALYSE, SURROUNDING_TEXT
}
@@ -1,14 +1,18 @@
package com.iqser.red.service.redaction.v1.model;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.legalbasis.LegalBasis;
import com.dslplatform.json.CompiledJson;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@Data
@CompiledJson
@AllArgsConstructor
@NoArgsConstructor
public class RedactionLog {
@@ -18,12 +22,18 @@ public class RedactionLog {
*/
private long analysisVersion;
private List<RedactionLogEntry> redactionLogEntry;
private List<LegalBasis> legalBasis;
/**
* Which analysis created this redactionLog.
*/
private int analysisNumber;
private List<RedactionLogEntry> redactionLogEntry = new ArrayList<>();
private List<RedactionLogLegalBasis> legalBasis = new ArrayList<>();
private long dictionaryVersion = -1;
private long dossierDictionaryVersion = -1;
private long rulesVersion = -1;
private long legalBasisVersion = -1;
}
@@ -0,0 +1,22 @@
package com.iqser.red.service.redaction.v1.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.OffsetDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RedactionLogComment {
private long id;
private String user;
private String text;
private String annotationId;
private String fileId;
private OffsetDateTime date;
private OffsetDateTime softDeletedTime;
}
@@ -1,17 +1,9 @@
package com.iqser.red.service.redaction.v1.model;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.AnnotationStatus;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.Comment;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
@Data
@@ -26,27 +18,29 @@ public class RedactionLogEntry {
private String value;
private String reason;
private int matchedRule;
private boolean rectangle;
private String legalBasis;
private boolean imported;
private boolean redacted;
private boolean isHint;
private boolean isRecommendation;
private boolean isFalsePositive;
private String section;
private float[] color;
@Builder.Default
private List<Rectangle> positions = new ArrayList<>();
private int sectionNumber;
private boolean manual;
private AnnotationStatus status;
private ManualRedactionType manualRedactionType;
private String manualRedactionUserId;
private boolean isDictionaryEntry;
private String textBefore;
private String textAfter;
@Builder.Default
private List<Comment> comments = new ArrayList<>();
private List<RedactionLogComment> comments = new ArrayList<>();
private int startOffset;
private int endOffset;
@@ -54,21 +48,44 @@ public class RedactionLogEntry {
private boolean isImage;
private boolean imageHasTransparency;
private boolean isDictionaryEntry;
private boolean isDossierDictionaryEntry;
private boolean excluded;
private String recategorizationType;
private String legalBasisChangeValue;
private String sourceId;
@EqualsAndHashCode.Exclude
@Builder.Default
private List<Change> changes = new ArrayList<>();
private Set<Engine> engines= new HashSet<>();
@EqualsAndHashCode.Exclude
@Builder.Default
private List<ManualChange> manualChanges = new ArrayList<>();
private Set<Engine> engines = new HashSet<>();
private Set<String> reference = new HashSet<>();
@Builder.Default
private Set<String> importedRedactionIntersections = new HashSet<>();
public boolean lastChangeIsRemoved() {
return last(changes).map(c -> c.getType() == ChangeType.REMOVED).orElse(false);
}
public boolean isLocalManualRedaction() {
return manualChanges.stream().anyMatch(mc -> mc.getManualRedactionType() == ManualRedactionType.ADD_LOCALLY &&
mc.getAnnotationStatus() == AnnotationStatus.APPROVED);
}
public boolean isManuallyRemoved() {
return manualChanges.stream().anyMatch(mc -> mc.getManualRedactionType() == ManualRedactionType.REMOVE_LOCALLY &&
mc.getAnnotationStatus() == AnnotationStatus.APPROVED);
}
private <T> Optional<T> last(List<T> list) {
return list.isEmpty() ? Optional.empty() : Optional.of(list.get(list.size() - 1));
}
}
@@ -1,17 +1,16 @@
package com.iqser.red.service.redaction.v1.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AnnotateRequest {
public class RedactionLogLegalBasis {
private String name;
private String description;
private String reason;
private String dossierId;
private String dossierTemplateId;
private String fileId;
}
@@ -1,15 +1,17 @@
package com.iqser.red.service.redaction.v1.model;
import java.util.HashSet;
import java.util.Set;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.ManualRedactions;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.configuration.Colors;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.type.Type;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Data
@Builder
@NoArgsConstructor
@@ -22,4 +24,9 @@ public class RedactionRequest {
private ManualRedactions manualRedactions;
@Builder.Default
private Set<Integer> excludedPages = new HashSet<>();
private Colors colors;
private List<Type> types;
private boolean includeFalsePositives;
}
@@ -3,31 +3,25 @@ package com.iqser.red.service.redaction.v1.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@Data
@RequiredArgsConstructor
@NoArgsConstructor
@AllArgsConstructor
public class SectionArea {
@NonNull
private Point topLeft;
@NonNull
private float width;
@NonNull
private float height;
@NonNull
private int page;
private String header;
public boolean contains(Rectangle other) {
return page == other.getPage() && this.topLeft.getX() <= other.getTopLeft().getX() && this.topLeft.getX() + this.getWidth() >= other.getTopLeft().getX() + other.getWidth() && this.getTopLeft().getY() <= other.getTopLeft().getY() && this.getTopLeft().getY() + this.getHeight() >= other.getTopLeft().getY() + other.getHeight();
}
// TODO we should only use one rectangle class.
public boolean contains(com.iqser.red.service.persistence.service.v1.api.model.annotations.Rectangle other) {
return page == other.getPage() && this.topLeft.getX() <= other.getTopLeftX() && this.topLeft.getX() + this.getWidth() >= other.getTopLeftX() + other.getWidth() && this.getTopLeft().getY() <= other.getTopLeftY() && this.getTopLeft().getY() + this.getHeight() >= other.getTopLeftY() + other.getHeight();
}
}
@@ -1,18 +1,31 @@
package com.iqser.red.service.redaction.v1.model;
import com.dslplatform.json.CompiledJson;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
@Data
@CompiledJson
@AllArgsConstructor
@NoArgsConstructor
public class SectionGrid {
private Map<Integer, List<SectionRectangle>> rectanglesPerPage = new HashMap<>();
private List<SectionGridSection> sections = new ArrayList<>();
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class SectionGridSection {
private int sectionNumber;
private String headline;
private Set<Integer> pages;
private List<SectionArea> sectionAreas;
}
}
@@ -3,30 +3,18 @@ package com.iqser.red.service.redaction.v1.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@RequiredArgsConstructor
public class SectionRectangle {
@NonNull
private Point topLeft;
@NonNull
private float width;
@NonNull
private float height;
@NonNull
private int part;
@NonNull
private int numberOfParts;
private List<CellRectangle> tableCells;
@@ -1,6 +1,9 @@
package com.iqser.red.service.redaction.v1.resources;
import com.iqser.red.service.redaction.v1.model.*;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.ManualRedactions;
import com.iqser.red.service.redaction.v1.model.RedactionLog;
import com.iqser.red.service.redaction.v1.model.RedactionRequest;
import com.iqser.red.service.redaction.v1.model.RedactionResult;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@@ -8,22 +11,29 @@ import org.springframework.web.bind.annotation.RequestBody;
public interface RedactionResource {
@PostMapping(value = "/annotate", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
AnnotateResponse annotate(@RequestBody AnnotateRequest annotateRequest);
@PostMapping(value = "/debug/classifications", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
RedactionResult classify(@RequestBody RedactionRequest redactionRequest);
@PostMapping(value = "/debug/sections", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
RedactionResult sections(@RequestBody RedactionRequest redactionRequest);
@PostMapping(value = "/debug/htmlTables", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
RedactionResult htmlTables(@RequestBody RedactionRequest redactionRequest);
@PostMapping(value = "/rules/test", consumes = MediaType.APPLICATION_JSON_VALUE)
void testRules(@RequestBody String rules);
@PostMapping(value = "/redaction-log/preview", consumes = MediaType.APPLICATION_JSON_VALUE)
RedactionLog getRedactionLog(@RequestBody RedactionRequest redactionRequest);
@PostMapping(value = "/manual/surrounding-text/{dossierId}/{fileId}", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
ManualRedactions addSurroundingText(@PathVariable("dossierId") String dossierId,
@PathVariable("fileId") String fileId,
@RequestBody ManualRedactions manualRedactions);
}
@@ -11,11 +11,51 @@
<artifactId>redaction-service-server-v1</artifactId>
<properties>
<drools.version>7.68.0.Final</drools.version>
<kie.version>7.68.0.Final</kie.version>
<locationtech.version>1.18.2</locationtech.version>
<javaassist.version>3.28.0-GA</javaassist.version>
<ahocorasick.version>0.6.3</ahocorasick.version>
<jackson.version>2.13.2</jackson.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.iqser.red.commons</groupId>
<artifactId>storage-commons</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-afterburner</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.ahocorasick</groupId>
<artifactId>ahocorasick</artifactId>
<version>${ahocorasick.version}</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>${javaassist.version}</version>
</dependency>
<dependency>
<groupId>com.iqser.red.service</groupId>
<artifactId>redaction-service-api-v1</artifactId>
@@ -24,39 +64,23 @@
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>7.59.0.Final</version>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<version>7.59.0.Final</version>
<version>${kie.version}</version>
</dependency>
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
<version>1.16.1</version>
<version>${locationtech.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>jbig2-imageio</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>com.github.jai-imageio</groupId>
<artifactId>jai-imageio-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>com.github.jai-imageio</groupId>
<artifactId>jai-imageio-jpeg2000</artifactId>
<version>1.4.0</version>
</dependency>
<!-- commons -->
<dependency>
<groupId>com.iqser.red.commons</groupId>
@@ -105,6 +129,18 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessors>
<annotationProcessor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor</annotationProcessor>
<annotationProcessor>com.dslplatform.json.processor.CompiledJsonAnnotationProcessor</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
<plugin>
<!-- generate git.properties for exposure in /info -->
<groupId>pl.project13.maven</groupId>
@@ -9,8 +9,12 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import io.micrometer.core.aop.TimedAspect;
import io.micrometer.core.instrument.MeterRegistry;
@Import({DefaultWebMvcConfiguration.class})
@EnableFeignClients(basePackageClasses = RulesClient.class)
@EnableConfigurationProperties(RedactionServiceSettings.class)
@@ -22,5 +26,10 @@ public class Application {
SpringApplication.run(Application.class, args);
}
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
@@ -1,5 +1,6 @@
package com.iqser.red.service.redaction.v1.server.classification.model;
import com.dslplatform.json.JsonAttribute;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
import lombok.AllArgsConstructor;
@@ -14,6 +15,7 @@ public class Footer {
private List<TextBlock> textBlocks;
@JsonIgnore
@JsonAttribute(ignore = true)
public SearchableText getSearchableText() {
SearchableText searchableText = new SearchableText();
@@ -1,5 +1,6 @@
package com.iqser.red.service.redaction.v1.server.classification.model;
import com.dslplatform.json.JsonAttribute;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
import lombok.AllArgsConstructor;
@@ -14,6 +15,7 @@ public class Header {
private List<TextBlock> textBlocks;
@JsonIgnore
@JsonAttribute(ignore = true)
public SearchableText getSearchableText() {
SearchableText searchableText = new SearchableText();
@@ -7,6 +7,7 @@ import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@Data
@@ -16,7 +17,7 @@ public class Page {
@NonNull
private List<AbstractTextContainer> textBlocks;
private List<PdfImage> images;
private List<PdfImage> images = new ArrayList<>();
private Rectangle bodyTextFrame;
@@ -1,5 +1,7 @@
package com.iqser.red.service.redaction.v1.server.classification.model;
import com.dslplatform.json.CompiledJson;
import com.dslplatform.json.JsonAttribute;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.iqser.red.service.redaction.v1.model.SectionArea;
import com.iqser.red.service.redaction.v1.server.redaction.model.CellValue;
@@ -14,6 +16,7 @@ import java.util.*;
@Data
@Builder
@CompiledJson
@NoArgsConstructor
@AllArgsConstructor
public class SectionText {
@@ -38,6 +41,7 @@ public class SectionText {
}
@JsonIgnore
@JsonAttribute(ignore = true)
public SearchableText getSearchableText() {
SearchableText searchableText = new SearchableText();
@@ -0,0 +1,20 @@
package com.iqser.red.service.redaction.v1.server.classification.model;
import com.dslplatform.json.CompiledJson;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@CompiledJson
@NoArgsConstructor
@AllArgsConstructor
public class SimplifiedSectionText {
private int sectionNumber;
private String text;
}
@@ -0,0 +1,23 @@
package com.iqser.red.service.redaction.v1.server.classification.model;
import java.util.ArrayList;
import java.util.List;
import com.dslplatform.json.CompiledJson;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@CompiledJson
@NoArgsConstructor
@AllArgsConstructor
public class SimplifiedText {
private int numberOfPages;
private List<SimplifiedSectionText> sectionTexts = new ArrayList<>();
}
@@ -1,5 +1,6 @@
package com.iqser.red.service.redaction.v1.server.classification.model;
import com.dslplatform.json.CompiledJson;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -8,6 +9,7 @@ import java.util.ArrayList;
import java.util.List;
@Data
@CompiledJson
@NoArgsConstructor
@AllArgsConstructor
public class Text {
@@ -1,5 +1,7 @@
package com.iqser.red.service.redaction.v1.server.classification.model;
import com.dslplatform.json.CompiledJson;
import com.dslplatform.json.JsonAttribute;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.iqser.red.service.redaction.v1.server.parsing.model.TextPositionSequence;
import com.iqser.red.service.redaction.v1.server.redaction.utils.TextNormalizationUtilities;
@@ -15,6 +17,7 @@ import java.util.List;
@AllArgsConstructor
@Builder
@Data
@CompiledJson
@NoArgsConstructor
public class TextBlock extends AbstractTextContainer {
@@ -121,6 +124,7 @@ public class TextBlock extends AbstractTextContainer {
@Override
@JsonIgnore
@JsonAttribute(ignore = true)
public String getText() {
StringBuilder sb = new StringBuilder();
@@ -1,5 +1,6 @@
package com.iqser.red.service.redaction.v1.server.classification.model;
import com.dslplatform.json.JsonAttribute;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
import lombok.AllArgsConstructor;
@@ -14,6 +15,7 @@ public class UnclassifiedText {
private List<TextBlock> textBlocks;
@JsonIgnore
@JsonAttribute(ignore = true)
public SearchableText getSearchableText() {
SearchableText searchableText = new SearchableText();
@@ -1,5 +1,7 @@
package com.iqser.red.service.redaction.v1.server.classification.service;
import static java.util.stream.Collectors.toSet;
import com.iqser.red.service.redaction.v1.server.classification.model.FloatFrequencyCounter;
import com.iqser.red.service.redaction.v1.server.classification.model.Orientation;
import com.iqser.red.service.redaction.v1.server.classification.model.Page;
@@ -16,6 +18,7 @@ import com.iqser.red.service.redaction.v1.server.tableextraction.model.Table;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
@@ -25,9 +28,12 @@ public class BlockificationService {
static final float THRESHOLD = 1f;
public Page blockify(List<TextPositionSequence> textPositions, List<Ruling> horizontalRulingLines,
List<Ruling> verticalRulingLines) {
sortRotatedSequences(textPositions);
List<TextPositionSequence> chunkWords = new ArrayList<>();
List<AbstractTextContainer> chunkBlockList1 = new ArrayList<>();
@@ -50,7 +56,7 @@ public class BlockificationService {
if (prev != null && (lineSeparation || startFromTop || splitByX || newLineAfterSplit || splittedByRuling)) {
Orientation prevOrientation = null;
if(!chunkBlockList1.isEmpty()) {
if (!chunkBlockList1.isEmpty()) {
prevOrientation = chunkBlockList1.get(chunkBlockList1.size() - 1).getOrientation();
}
@@ -62,15 +68,11 @@ public class BlockificationService {
wasSplitted = true;
cb1.setOrientation(Orientation.LEFT);
splitX1 = word.getX1();
} else
if (newLineAfterSplit && !splittedByRuling) {
} else if (newLineAfterSplit && !splittedByRuling) {
wasSplitted = false;
cb1.setOrientation(Orientation.RIGHT);
splitX1 = null;
} else
if(prevOrientation != null && prevOrientation.equals(Orientation.RIGHT) && (lineSeparation || !startFromTop || !splitByX || !newLineAfterSplit || !splittedByRuling)){
} else if (prevOrientation != null && prevOrientation.equals(Orientation.RIGHT) && (lineSeparation || !startFromTop || !splitByX || !newLineAfterSplit || !splittedByRuling)) {
cb1.setOrientation(Orientation.LEFT);
}
@@ -110,16 +112,18 @@ public class BlockificationService {
while (itty.hasNext()) {
TextBlock block = (TextBlock) itty.next();
if(previousLeft != null && block.getOrientation().equals(Orientation.LEFT)){
if (previousLeft.getMinY() > block.getMinY() && block.getMaxY() + block.getMostPopularWordHeight() > previousLeft.getMinY()){
if (previousLeft != null && block.getOrientation().equals(Orientation.LEFT)) {
if (previousLeft.getMinY() > block.getMinY() && block.getMaxY() + block.getMostPopularWordHeight() > previousLeft
.getMinY()) {
previousLeft.add(block);
itty.remove();
continue;
}
}
if(previousRight != null && block.getOrientation().equals(Orientation.RIGHT)){
if (previousRight.getMinY() > block.getMinY() && block.getMaxY() + block.getMostPopularWordHeight() > previousRight.getMinY()){
if (previousRight != null && block.getOrientation().equals(Orientation.RIGHT)) {
if (previousRight.getMinY() > block.getMinY() && block.getMaxY() + block.getMostPopularWordHeight() > previousRight
.getMinY()) {
previousRight.add(block);
itty.remove();
continue;
@@ -133,16 +137,16 @@ public class BlockificationService {
}
}
itty = chunkBlockList1.iterator();
TextBlock previous = null;
while (itty.hasNext()) {
TextBlock block = (TextBlock) itty.next();
if(previous != null && previous.getOrientation().equals(Orientation.LEFT) && block.getOrientation().equals(Orientation.LEFT) && equalsWithThreshold(block.getMaxY(), previous
.getMaxY())||
previous != null && previous.getOrientation().equals(Orientation.LEFT) && block.getOrientation().equals(Orientation.RIGHT) && equalsWithThreshold(block.getMaxY(), previous
.getMaxY())){
if (previous != null && previous.getOrientation().equals(Orientation.LEFT) && block.getOrientation()
.equals(Orientation.LEFT) && equalsWithThreshold(block.getMaxY(), previous.getMaxY()) || previous != null && previous
.getOrientation()
.equals(Orientation.LEFT) && block.getOrientation()
.equals(Orientation.RIGHT) && equalsWithThreshold(block.getMaxY(), previous.getMaxY())) {
previous.add(block);
itty.remove();
continue;
@@ -151,11 +155,12 @@ public class BlockificationService {
previous = block;
}
return new Page(chunkBlockList1);
}
private boolean equalsWithThreshold(float f1, float f2){
private boolean equalsWithThreshold(float f1, float f2) {
return Math.abs(f1 - f2) < THRESHOLD;
}
@@ -197,6 +202,13 @@ public class BlockificationService {
textBlock.setHighestFontSize(fontSizeFrequencyCounter.getHighest());
}
if (textBlock != null && textBlock.getSequences() != null && textBlock.getSequences()
.stream()
.map(t -> round(t.getY1(), 3))
.collect(toSet())
.size() == 1) {
textBlock.getSequences().sort(Comparator.comparing(TextPositionSequence::getX1));
}
return textBlock;
}
@@ -291,4 +303,30 @@ public class BlockificationService {
return new Rectangle(minY, minX, maxX - minX, maxY - minY);
}
private void sortRotatedSequences(List<TextPositionSequence> sequences) {
List<TextPositionSequence> rotatedWords = new ArrayList<>();
Iterator<TextPositionSequence> itty = sequences.iterator();
while (itty.hasNext()) {
var pos = itty.next();
if (pos.getTextPositions().get(0).getDir() == 270) {
rotatedWords.add(pos);
itty.remove();
}
}
if (!rotatedWords.isEmpty() && !sequences.isEmpty()) {
rotatedWords.sort(Comparator.comparing(TextPositionSequence::getX1));
}
sequences.addAll(rotatedWords);
}
private double round(float value, int decimalPoints) {
var d = Math.pow(10, decimalPoints);
return Math.round(value * d) / d;
}
}
@@ -52,7 +52,7 @@ public class ClassificationService {
List<Float> headlineFontSizes) {
if (document.getFontSizeCounter().getMostPopular() == null) {
// TODO Figure out why this happens.
textBlock.setClassification("Other");
return;
}
if (PositionUtils.isOverBodyTextFrame(bodyTextFrame, textBlock, page.isRotated()) && (document.getFontSizeCounter()
@@ -73,8 +73,10 @@ public class ClassificationService {
}
} else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock) && textBlock.getMostPopularWordFontSize() > document
.getFontSizeCounter()
.getMostPopular() && PositionUtils.getApproxLineCount(textBlock) < 4.9 && textBlock.getMostPopularWordStyle()
.equals("bold")) {
.getMostPopular() && PositionUtils.getApproxLineCount(textBlock) < 4.9 && (textBlock.getMostPopularWordStyle()
.equals("bold") || !document.getFontStyleCounter().getCountPerValue().containsKey("bold") && textBlock.getMostPopularWordFontSize() > document
.getFontSizeCounter()
.getMostPopular() + 1) && textBlock.getSequences().get(0).getTextPositions().get(0).getFontSizeInPt() >= textBlock.getMostPopularWordFontSize()) {
for (int i = 1; i <= headlineFontSizes.size(); i++) {
if (textBlock.getMostPopularWordFontSize() == headlineFontSizes.get(i - 1)) {
@@ -86,7 +88,7 @@ public class ClassificationService {
.startsWith("Figure ") && PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock) && textBlock.getMostPopularWordStyle()
.equals("bold") && !document.getFontStyleCounter()
.getMostPopular()
.equals("bold") && PositionUtils.getApproxLineCount(textBlock) < 2.9) {
.equals("bold") && PositionUtils.getApproxLineCount(textBlock) < 2.9 && textBlock.getSequences().get(0).getTextPositions().get(0).getFontSizeInPt() >= textBlock.getMostPopularWordFontSize()) {
textBlock.setClassification("H " + (headlineFontSizes.size() + 1));
document.setHeadlines(true);
} else if (PositionUtils.isWithinBodyTextFrame(bodyTextFrame, textBlock) && textBlock.getMostPopularWordFontSize() == document
@@ -1,13 +1,9 @@
package com.iqser.red.service.redaction.v1.server.client;
import java.util.List;
import java.util.Map;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import com.iqser.red.service.redaction.v1.server.client.model.EntityRecogintionEntity;
import com.iqser.red.service.redaction.v1.server.client.model.EntityRecognitionRequest;
import com.iqser.red.service.redaction.v1.server.client.model.NerEntities;
@@ -16,4 +12,5 @@ public interface EntityRecognitionClient {
@PostMapping(value = "/find_authors", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
NerEntities findAuthors(EntityRecognitionRequest entityRecognitionRequest);
}
@@ -1,15 +0,0 @@
package com.iqser.red.service.redaction.v1.server.client;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.multipart.MultipartFile;
@FeignClient(name = "ImageClassificationResource", url = "${image-service.url}")
public interface ImageClassificationClient {
@PostMapping(value = "/process_full_img", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
ImageClassificationResponse classify(@RequestBody MultipartFile file);
}
@@ -1,13 +0,0 @@
package com.iqser.red.service.redaction.v1.server.client;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ImageClassificationResponse {
private String category;
}
@@ -1,16 +1,16 @@
package com.iqser.red.service.redaction.v1.server.client;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
public class MockMultipartFile implements MultipartFile {
private final String name;
@@ -82,13 +82,13 @@ public class MockMultipartFile implements MultipartFile {
}
public byte[] getBytes() throws IOException {
public byte[] getBytes() {
return this.content;
}
public InputStream getInputStream() throws IOException {
public InputStream getInputStream() {
return new ByteArrayInputStream(this.content);
}
@@ -1,5 +1,6 @@
package com.iqser.red.service.redaction.v1.server.client.model;
import com.dslplatform.json.CompiledJson;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -7,6 +8,7 @@ import lombok.NoArgsConstructor;
@Data
@Builder
@CompiledJson
@AllArgsConstructor
@NoArgsConstructor
public class EntityRecogintionEntity {
@@ -4,18 +4,18 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.dslplatform.json.CompiledJson;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@CompiledJson
@NoArgsConstructor
@AllArgsConstructor
public class NerEntities {
@Builder.Default
private Map<Integer, List<EntityRecogintionEntity>> result = new HashMap<>();
private Map<Integer, List<EntityRecogintionEntity>> data = new HashMap<>();
}
@@ -1,6 +1,7 @@
package com.iqser.red.service.redaction.v1.server.controller;
import com.iqser.red.commons.spring.ErrorMessage;
import com.iqser.red.service.redaction.v1.server.exception.NotFoundException;
import com.iqser.red.service.redaction.v1.server.exception.RulesValidationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
@@ -36,4 +37,11 @@ public class ControllerAdvice {
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
@ResponseBody
@ResponseStatus(value = HttpStatus.NOT_FOUND)
@ExceptionHandler(value = NotFoundException.class)
public ErrorMessage handleFileNotFoundException(NotFoundException e) {
return new ErrorMessage(OffsetDateTime.now(), e.getMessage());
}
}
@@ -1,16 +1,14 @@
package com.iqser.red.service.redaction.v1.server.controller;
import com.amazonaws.services.kms.model.NotFoundException;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.ManualRedactions;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileType;
import com.iqser.red.service.redaction.v1.model.*;
import com.iqser.red.service.redaction.v1.resources.RedactionResource;
import com.iqser.red.service.redaction.v1.server.classification.model.Document;
import com.iqser.red.service.redaction.v1.server.classification.model.Page;
import com.iqser.red.service.redaction.v1.server.exception.NotFoundException;
import com.iqser.red.service.redaction.v1.server.exception.RedactionException;
import com.iqser.red.service.redaction.v1.server.redaction.service.AnnotationService;
import com.iqser.red.service.redaction.v1.server.redaction.service.DictionaryService;
import com.iqser.red.service.redaction.v1.server.redaction.service.DroolsExecutionService;
import com.iqser.red.service.redaction.v1.server.redaction.service.RedactionLogMergeService;
import com.iqser.red.service.redaction.v1.server.redaction.service.*;
import com.iqser.red.service.redaction.v1.server.segmentation.PdfSegmentationService;
import com.iqser.red.service.redaction.v1.server.storage.RedactionStorageService;
import com.iqser.red.service.redaction.v1.server.tableextraction.model.AbstractTextContainer;
@@ -18,13 +16,14 @@ import com.iqser.red.service.redaction.v1.server.tableextraction.model.Table;
import com.iqser.red.service.redaction.v1.server.visualization.service.PdfVisualisationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.stream.Collectors;
@Slf4j
@RestController
@@ -33,40 +32,17 @@ public class RedactionController implements RedactionResource {
private final PdfVisualisationService pdfVisualisationService;
private final DroolsExecutionService droolsExecutionService;
private final DictionaryService dictionaryService;
private final AnnotationService annotationService;
private final PdfSegmentationService pdfSegmentationService;
private final RedactionStorageService redactionStorageService;
private final RedactionLogMergeService redactionLogMergeService;
public AnnotateResponse annotate(@RequestBody AnnotateRequest annotateRequest) {
var storedObjectStream = redactionStorageService.getStoredObject(RedactionStorageService.StorageIdUtils.getStorageId(annotateRequest.getDossierId(), annotateRequest.getFileId(), FileType.ORIGIN));
var redactionLog = redactionStorageService.getRedactionLog(annotateRequest.getDossierId(), annotateRequest.getFileId());
var sectionsGrid = redactionStorageService.getSectionGrid(annotateRequest.getDossierId(), annotateRequest.getFileId());
try (PDDocument pdDocument = PDDocument.load(storedObjectStream, MemoryUsageSetting.setupTempFileOnly())) {
pdDocument.setAllSecurityToBeRemoved(true);
dictionaryService.updateDictionary(annotateRequest.getDossierTemplateId(), annotateRequest.getDossierId());
annotationService.annotate(pdDocument, redactionLog, sectionsGrid);
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
pdDocument.save(byteArrayOutputStream);
return AnnotateResponse.builder().document(byteArrayOutputStream.toByteArray()).build();
}
} catch (Exception e) {
throw new RedactionException(e);
}
}
private final ManualRedactionSurroundingTextService manualRedactionSurroundingTextService;
@Override
public RedactionResult classify(@RequestBody RedactionRequest redactionRequest) {
var storedObjectStream = redactionStorageService.getStoredObject(RedactionStorageService.StorageIdUtils.getStorageId(redactionRequest.getDossierId(), redactionRequest.getFileId(), FileType.ORIGIN));
try {
Document classifiedDoc = pdfSegmentationService.parseDocument(storedObjectStream);
Document classifiedDoc = pdfSegmentationService.parseDocument(storedObjectStream, null);
storedObjectStream = redactionStorageService.getStoredObject(RedactionStorageService.StorageIdUtils.getStorageId(redactionRequest.getDossierId(), redactionRequest.getFileId(), FileType.ORIGIN));
try (PDDocument pdDocument = PDDocument.load(storedObjectStream)) {
@@ -84,14 +60,15 @@ public class RedactionController implements RedactionResource {
throw new RedactionException(e);
}
}
@Override
public RedactionResult sections(@RequestBody RedactionRequest redactionRequest) {
var storedObjectStream = redactionStorageService.getStoredObject(RedactionStorageService.StorageIdUtils.getStorageId(redactionRequest.getDossierId(), redactionRequest.getFileId(), FileType.ORIGIN));
try {
Document classifiedDoc = pdfSegmentationService.parseDocument(storedObjectStream);
Document classifiedDoc = pdfSegmentationService.parseDocument(storedObjectStream, null);
storedObjectStream = redactionStorageService.getStoredObject(RedactionStorageService.StorageIdUtils.getStorageId(redactionRequest.getDossierId(), redactionRequest.getFileId(), FileType.ORIGIN));
try (PDDocument pdDocument = PDDocument.load(storedObjectStream)) {
@@ -108,7 +85,6 @@ public class RedactionController implements RedactionResource {
throw new RedactionException(e);
}
}
@@ -119,12 +95,11 @@ public class RedactionController implements RedactionResource {
try {
var storedObjectStream = redactionStorageService.getStoredObject(RedactionStorageService.StorageIdUtils.getStorageId(redactionRequest.getDossierId(), redactionRequest.getFileId(), FileType.ORIGIN));
classifiedDoc = pdfSegmentationService.parseDocument(storedObjectStream, true);
classifiedDoc = pdfSegmentationService.parseDocument(storedObjectStream, null);
} catch (Exception e) {
throw new RedactionException(e);
}
StringBuilder sb = new StringBuilder();
for (Page page : classifiedDoc.getPages()) {
for (AbstractTextContainer textContainer : page.getTextBlocks()) {
@@ -139,33 +114,22 @@ public class RedactionController implements RedactionResource {
}
@Override
public void testRules(@RequestBody String rules) {
droolsExecutionService.testRules(rules);
}
@Override
public RedactionLog getRedactionLog(RedactionRequest redactionRequest) {
log.info("Requested preview for: {}", redactionRequest);
dictionaryService.updateDictionary(redactionRequest.getDossierTemplateId(), redactionRequest.getDossierId());
var redactionLog = redactionStorageService.getRedactionLog(redactionRequest.getDossierId(), redactionRequest.getFileId());
if (redactionLog == null) {
throw new NotFoundException("RedactionLog not present");
}
log.info("Loaded redaction log with computationalVersion: {}", redactionLog.getAnalysisVersion());
if (redactionLog.getAnalysisVersion() == 0) {
// old redaction logs are returned directly
return redactionLog;
} else {
return redactionLogMergeService.mergeRedactionLogData(redactionLog, redactionRequest.getDossierTemplateId(), redactionRequest.getManualRedactions(), redactionRequest.getExcludedPages());
}
return redactionLogMergeService.provideRedactionLog(redactionRequest);
}
private RedactionResult convert(PDDocument document, int numberOfPages) throws IOException {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
@@ -179,4 +143,14 @@ public class RedactionController implements RedactionResource {
}
@Override
public ManualRedactions addSurroundingText(@PathVariable("dossierId") String dossierId,
@PathVariable("fileId") String fileId,
@RequestBody ManualRedactions manualRedactions) {
var result = manualRedactionSurroundingTextService.addSurroundingText(dossierId, fileId, manualRedactions);
log.info("Added surrounding text for manual redaction in dossierId {} and fileId {} took: {}", dossierId, fileId, result.getDuration());
return result.getManualRedactions();
}
}
@@ -0,0 +1,9 @@
package com.iqser.red.service.redaction.v1.server.exception;
public class NotFoundException extends RuntimeException {
public NotFoundException(String message) {
super(message);
}
}
@@ -1,52 +0,0 @@
package com.iqser.red.service.redaction.v1.server.memory;
import lombok.extern.slf4j.Slf4j;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
@Slf4j
public class MemoryStats {
public static void printMemoryStats() {
log.info("\n\n ------------------------------ \n" +
" Used Memory: " + humanReadableByteCountBin(getUsedMemory()) + "\n" +
" Free Memory: " + humanReadableByteCountBin(getFreeMemory()) + "\n" +
" Total Memory: " + humanReadableByteCountBin(getTotalMemory()) + "\n" +
" Max Memory: " + humanReadableByteCountBin(getMaxMemory()) + "\n" +
"\n ------------------------------ \n");
}
public static String humanReadableByteCountBin(long bytes) {
long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes);
if (absB < 1024) {
return bytes + " B";
}
long value = absB;
CharacterIterator ci = new StringCharacterIterator("KMGTPE");
for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) {
value >>= 10;
ci.next();
}
value *= Long.signum(bytes);
return String.format("%.1f %ciB", value / 1024.0, ci.current());
}
private static long getMaxMemory() {
return Runtime.getRuntime().maxMemory();
}
private static long getUsedMemory() {
return getMaxMemory() - getFreeMemory();
}
private static long getTotalMemory() {
return Runtime.getRuntime().totalMemory();
}
private static long getFreeMemory() {
return Runtime.getRuntime().freeMemory();
}
}
@@ -0,0 +1,389 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.iqser.red.service.redaction.v1.server.parsing;
import java.io.InputStream;
import java.io.IOException;
import java.util.Map;
import java.util.WeakHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fontbox.ttf.TrueTypeFont;
import org.apache.fontbox.util.BoundingBox;
import org.apache.pdfbox.contentstream.PDFStreamEngine;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.font.encoding.GlyphList;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDCIDFont;
import org.apache.pdfbox.pdmodel.font.PDCIDFontType2;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDSimpleFont;
import org.apache.pdfbox.pdmodel.font.PDTrueTypeFont;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.apache.pdfbox.pdmodel.font.PDType3Font;
import org.apache.pdfbox.pdmodel.graphics.state.PDGraphicsState;
import org.apache.pdfbox.text.TextPosition;
import org.apache.pdfbox.util.Matrix;
import org.apache.pdfbox.util.Vector;
import org.apache.pdfbox.contentstream.operator.DrawObject;
import org.apache.pdfbox.contentstream.operator.state.Concatenate;
import org.apache.pdfbox.contentstream.operator.state.Restore;
import org.apache.pdfbox.contentstream.operator.state.Save;
import org.apache.pdfbox.contentstream.operator.state.SetGraphicsStateParameters;
import org.apache.pdfbox.contentstream.operator.state.SetMatrix;
import org.apache.pdfbox.contentstream.operator.text.BeginText;
import org.apache.pdfbox.contentstream.operator.text.EndText;
import org.apache.pdfbox.contentstream.operator.text.SetFontAndSize;
import org.apache.pdfbox.contentstream.operator.text.SetTextHorizontalScaling;
import org.apache.pdfbox.contentstream.operator.text.ShowTextAdjusted;
import org.apache.pdfbox.contentstream.operator.text.ShowTextLine;
import org.apache.pdfbox.contentstream.operator.text.ShowTextLineAndSpace;
import org.apache.pdfbox.contentstream.operator.text.MoveText;
import org.apache.pdfbox.contentstream.operator.text.MoveTextSetLeading;
import org.apache.pdfbox.contentstream.operator.text.NextLine;
import org.apache.pdfbox.contentstream.operator.text.SetCharSpacing;
import org.apache.pdfbox.contentstream.operator.text.SetTextLeading;
import org.apache.pdfbox.contentstream.operator.text.SetTextRenderingMode;
import org.apache.pdfbox.contentstream.operator.text.SetTextRise;
import org.apache.pdfbox.contentstream.operator.text.SetWordSpacing;
import org.apache.pdfbox.contentstream.operator.text.ShowText;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.pdmodel.font.PDFontDescriptor;
/**
* LEGACY text calculations which are known to be incorrect but are depended on by PDFTextStripper.
*
* This class exists only so that we don't break the code of users who have their own subclasses of
* PDFTextStripper. It replaces the mostly empty implementation of showGlyph() in PDFStreamEngine
* with a heuristic implementation which is backwards compatible.
*
* DO NOT USE THIS CODE UNLESS YOU ARE WORKING WITH PDFTextStripper.
* THIS CODE IS DELIBERATELY INCORRECT, USE PDFStreamEngine INSTEAD.
*/
@SuppressWarnings({"PMD", "checkstyle:all"})
class LegacyPDFStreamEngine extends PDFStreamEngine
{
private static final Log LOG = LogFactory.getLog(LegacyPDFStreamEngine.class);
private int pageRotation;
private PDRectangle pageSize;
private Matrix translateMatrix;
private final GlyphList glyphList;
private final Map<COSDictionary, Float> fontHeightMap = new WeakHashMap<COSDictionary, Float>();
/**
* Constructor.
*/
LegacyPDFStreamEngine() throws IOException
{
addOperator(new BeginText());
addOperator(new Concatenate());
addOperator(new DrawObject()); // special text version
addOperator(new EndText());
addOperator(new SetGraphicsStateParameters());
addOperator(new Save());
addOperator(new Restore());
addOperator(new NextLine());
addOperator(new SetCharSpacing());
addOperator(new MoveText());
addOperator(new MoveTextSetLeading());
addOperator(new SetFontAndSize());
addOperator(new ShowText());
addOperator(new ShowTextAdjusted());
addOperator(new SetTextLeading());
addOperator(new SetMatrix());
addOperator(new SetTextRenderingMode());
addOperator(new SetTextRise());
addOperator(new SetWordSpacing());
addOperator(new SetTextHorizontalScaling());
addOperator(new ShowTextLine());
addOperator(new ShowTextLineAndSpace());
// load additional glyph list for Unicode mapping
String path = "/org/apache/pdfbox/resources/glyphlist/additional.txt";
InputStream input = GlyphList.class.getResourceAsStream(path);
glyphList = new GlyphList(GlyphList.getAdobeGlyphList(), input);
}
/**
* This will initialize and process the contents of the stream.
*
* @param page the page to process
* @throws java.io.IOException if there is an error accessing the stream.
*/
@Override
public void processPage(PDPage page) throws IOException
{
this.pageRotation = page.getRotation();
this.pageSize = page.getCropBox();
if (pageSize.getLowerLeftX() == 0 && pageSize.getLowerLeftY() == 0)
{
translateMatrix = null;
}
else
{
// translation matrix for cropbox
translateMatrix = Matrix.getTranslateInstance(-pageSize.getLowerLeftX(), -pageSize.getLowerLeftY());
}
super.processPage(page);
}
/**
* Called when a glyph is to be processed. The heuristic calculations here were originally
* written by Ben Litchfield for PDFStreamEngine.
*/
@Override
protected void showGlyph(Matrix textRenderingMatrix, PDFont font, int code,
String unicode,
Vector displacement)
throws IOException
{
//
// legacy calculations which were previously in PDFStreamEngine
//
// DO NOT USE THIS CODE UNLESS YOU ARE WORKING WITH PDFTextStripper.
// THIS CODE IS DELIBERATELY INCORRECT
//
PDGraphicsState state = getGraphicsState();
Matrix ctm = state.getCurrentTransformationMatrix();
float fontSize = state.getTextState().getFontSize();
float horizontalScaling = state.getTextState().getHorizontalScaling() / 100f;
Matrix textMatrix = getTextMatrix();
float displacementX = displacement.getX();
// the sorting algorithm is based on the width of the character. As the displacement
// for vertical characters doesn't provide any suitable value for it, we have to
// calculate our own
if (font.isVertical())
{
displacementX = font.getWidth(code) / 1000;
// there may be an additional scaling factor for true type fonts
TrueTypeFont ttf = null;
if (font instanceof PDTrueTypeFont)
{
ttf = ((PDTrueTypeFont)font).getTrueTypeFont();
}
else if (font instanceof PDType0Font)
{
PDCIDFont cidFont = ((PDType0Font)font).getDescendantFont();
if (cidFont instanceof PDCIDFontType2)
{
ttf = ((PDCIDFontType2)cidFont).getTrueTypeFont();
}
}
if (ttf != null && ttf.getUnitsPerEm() != 1000)
{
displacementX *= 1000f / ttf.getUnitsPerEm();
}
}
//
// legacy calculations which were previously in PDFStreamEngine
//
// DO NOT USE THIS CODE UNLESS YOU ARE WORKING WITH PDFTextStripper.
// THIS CODE IS DELIBERATELY INCORRECT
//
// (modified) combined displacement, this is calculated *without* taking the character
// spacing and word spacing into account, due to legacy code in TextStripper
float tx = displacementX * fontSize * horizontalScaling;
float ty = displacement.getY() * fontSize;
// (modified) combined displacement matrix
Matrix td = Matrix.getTranslateInstance(tx, ty);
// (modified) text rendering matrix
Matrix nextTextRenderingMatrix = td.multiply(textMatrix).multiply(ctm); // text space -> device space
float nextX = nextTextRenderingMatrix.getTranslateX();
float nextY = nextTextRenderingMatrix.getTranslateY();
// (modified) width and height calculations
float dxDisplay = nextX - textRenderingMatrix.getTranslateX();
Float fontHeight = fontHeightMap.get(font.getCOSObject());
if (fontHeight == null)
{
fontHeight = computeFontHeight(font);
fontHeightMap.put(font.getCOSObject(), fontHeight);
}
float dyDisplay = fontHeight * textRenderingMatrix.getScalingFactorY();
//
// start of the original method
//
// Note on variable names. There are three different units being used in this code.
// Character sizes are given in glyph units, text locations are initially given in text
// units, and we want to save the data in display units. The variable names should end with
// Text or Disp to represent if the values are in text or disp units (no glyph units are
// saved).
float glyphSpaceToTextSpaceFactor = 1 / 1000f;
if (font instanceof PDType3Font)
{
glyphSpaceToTextSpaceFactor = font.getFontMatrix().getScaleX();
}
float spaceWidthText = 0;
try
{
// to avoid crash as described in PDFBOX-614, see what the space displacement should be
spaceWidthText = font.getSpaceWidth() * glyphSpaceToTextSpaceFactor;
}
catch (Throwable exception)
{
LOG.warn(exception, exception);
}
if (spaceWidthText == 0)
{
spaceWidthText = font.getAverageFontWidth() * glyphSpaceToTextSpaceFactor;
// the average space width appears to be higher than necessary so make it smaller
spaceWidthText *= .80f;
}
if (spaceWidthText == 0)
{
spaceWidthText = 1.0f; // if could not find font, use a generic value
}
// the space width has to be transformed into display units
float spaceWidthDisplay = spaceWidthText * textRenderingMatrix.getScalingFactorX();
// use our additional glyph list for Unicode mapping
String unicodeMapping = font.toUnicode(code, glyphList);
// when there is no Unicode mapping available, Acrobat simply coerces the character code
// into Unicode, so we do the same. Subclasses of PDFStreamEngine don't necessarily want
// this, which is why we leave it until this point in PDFTextStreamEngine.
if (unicodeMapping == null)
{
if (font instanceof PDSimpleFont)
{
char c = (char) code;
unicodeMapping = new String(new char[] { c });
}
else
{
// Acrobat doesn't seem to coerce composite font's character codes, instead it
// skips them. See the "allah2.pdf" TestTextStripper file.
return;
}
}
// adjust for cropbox if needed
Matrix translatedTextRenderingMatrix;
if (translateMatrix == null)
{
translatedTextRenderingMatrix = textRenderingMatrix;
}
else
{
translatedTextRenderingMatrix = Matrix.concatenate(translateMatrix, textRenderingMatrix);
nextX -= pageSize.getLowerLeftX();
nextY -= pageSize.getLowerLeftY();
}
// This is a hack for unicode letter with 2 chars e.g. RA see unicodeProblem.pdf
if(unicodeMapping.length() == 2){
processTextPosition(new TextPosition(pageRotation, pageSize.getWidth(),
pageSize.getHeight(), translatedTextRenderingMatrix, nextX, nextY,
Math.abs(dyDisplay), dxDisplay,
Math.abs(spaceWidthDisplay), Character.toString(unicodeMapping.charAt(0)), new int[] { code }, font,
fontSize,
(int)(fontSize * textMatrix.getScalingFactorX())));
processTextPosition(new TextPosition(pageRotation, pageSize.getWidth(),
pageSize.getHeight(), translatedTextRenderingMatrix, nextX, nextY,
Math.abs(dyDisplay), dxDisplay,
Math.abs(spaceWidthDisplay), Character.toString(unicodeMapping.charAt(1)), new int[] { code }, font,
fontSize,
(int)(fontSize * textMatrix.getScalingFactorX())));
} else {
processTextPosition(new TextPosition(pageRotation, pageSize.getWidth(), pageSize.getHeight(), translatedTextRenderingMatrix, nextX, nextY, Math.abs(dyDisplay), dxDisplay, Math.abs(spaceWidthDisplay), unicodeMapping, new int[]{code}, font, fontSize, (int) (fontSize * textMatrix.getScalingFactorX())));
}
}
/**
* Compute the font height. Override this if you want to use own calculations.
*
* @param font the font.
* @return the font height.
*
* @throws IOException if there is an error while getting the font bounding box.
*/
protected float computeFontHeight(PDFont font) throws IOException
{
BoundingBox bbox = font.getBoundingBox();
if (bbox.getLowerLeftY() < Short.MIN_VALUE)
{
// PDFBOX-2158 and PDFBOX-3130
// files by Salmat eSolutions / ClibPDF Library
bbox.setLowerLeftY(- (bbox.getLowerLeftY() + 65536));
}
// 1/2 the bbox is used as the height todo: why?
float glyphHeight = bbox.getHeight() / 2;
// sometimes the bbox has very high values, but CapHeight is OK
PDFontDescriptor fontDescriptor = font.getFontDescriptor();
if (fontDescriptor != null)
{
float capHeight = fontDescriptor.getCapHeight();
if (Float.compare(capHeight, 0) != 0 &&
(capHeight < glyphHeight || Float.compare(glyphHeight, 0) == 0))
{
glyphHeight = capHeight;
}
// PDFBOX-3464, PDFBOX-4480, PDFBOX-4553:
// sometimes even CapHeight has very high value, but Ascent and Descent are ok
float ascent = fontDescriptor.getAscent();
float descent = fontDescriptor.getDescent();
if (capHeight > ascent && ascent > 0 && descent < 0 &&
((ascent - descent) / 2 < glyphHeight || Float.compare(glyphHeight, 0) == 0))
{
glyphHeight = (ascent - descent) / 2;
}
}
// transformPoint from glyph space -> text space
float height;
if (font instanceof PDType3Font)
{
height = font.getFontMatrix().transformPoint(0, glyphHeight).y;
}
else
{
height = glyphHeight / 1000;
}
return height;
}
/**
* A method provided as an event interface to allow a subclass to perform some specific
* functionality when text needs to be processed.
*
* @param text The text to be processed.
*/
protected void processTextPosition(TextPosition text)
{
// subclasses can override to provide specific functionality
}
}
@@ -1,31 +1,23 @@
package com.iqser.red.service.redaction.v1.server.parsing;
import com.iqser.red.service.redaction.v1.server.parsing.model.RedTextPosition;
import com.iqser.red.service.redaction.v1.server.parsing.model.TextPositionSequence;
import com.iqser.red.service.redaction.v1.server.redaction.model.PdfImage;
import com.iqser.red.service.redaction.v1.server.tableextraction.model.Ruling;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.pdfbox.contentstream.operator.Operator;
import org.apache.pdfbox.contentstream.operator.OperatorName;
import org.apache.pdfbox.contentstream.operator.color.*;
import org.apache.pdfbox.contentstream.operator.state.*;
import org.apache.pdfbox.contentstream.operator.text.SetFontAndSize;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSNumber;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition;
import org.apache.pdfbox.util.Matrix;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -48,8 +40,6 @@ public class PDFLinesTextStripper extends PDFTextStripper {
private int minCharHeight;
@Getter
private int maxCharHeight;
@Getter
private List<PdfImage> images = new ArrayList<>();
private float path_x;
private float path_y;
@@ -106,11 +96,9 @@ public class PDFLinesTextStripper extends PDFTextStripper {
// The direction of vertical lines must always be from bottom to top for the table extraction algorithm.
if (pos.getY() > path_y) {
graphicsPath.add(new Ruling(new Point2D.Float(path_x, path_y), new Point2D.Float((float) pos.getX(), (float) pos
.getY())));
graphicsPath.add(new Ruling(new Point2D.Float(path_x, path_y), new Point2D.Float((float) pos.getX(), (float) pos.getY())));
} else {
graphicsPath.add(new Ruling(new Point2D.Float(path_x, (float) pos.getY()), new Point2D.Float((float) pos
.getX(), path_y)));
graphicsPath.add(new Ruling(new Point2D.Float(path_x, (float) pos.getY()), new Point2D.Float((float) pos.getX(), path_y)));
}
path_x = (float) pos.getX();
@@ -131,25 +119,19 @@ public class PDFLinesTextStripper extends PDFTextStripper {
Point2D p2 = transformPosition(x + width, y + height);
// Horizontal lines
graphicsPath.add(new Ruling(new Point2D.Float((float) p1.getX(), (float) p1.getY()), new Point2D.Float((float) p2
.getX(), (float) p1.getY())));
graphicsPath.add(new Ruling(new Point2D.Float((float) p1.getX(), (float) p2.getY()), new Point2D.Float((float) p2
.getX(), (float) p2.getY())));
graphicsPath.add(new Ruling(new Point2D.Float((float) p1.getX(), (float) p1.getY()), new Point2D.Float((float) p2.getX(), (float) p1.getY())));
graphicsPath.add(new Ruling(new Point2D.Float((float) p1.getX(), (float) p2.getY()), new Point2D.Float((float) p2.getX(), (float) p2.getY())));
// Vertical lines, direction must always be from bottom to top for the table extraction algorithm.
if (p2.getY() > p1.getY()) {
graphicsPath.add(new Ruling(new Point2D.Float((float) p2.getX(), (float) p1.getY()), new Point2D.Float((float) p2
.getX(), (float) p2.getY())));
graphicsPath.add(new Ruling(new Point2D.Float((float) p2.getX(), (float) p1.getY()), new Point2D.Float((float) p2.getX(), (float) p2.getY())));
} else {
graphicsPath.add(new Ruling(new Point2D.Float((float) p2.getX(), (float) p2.getY()), new Point2D.Float((float) p2
.getX(), (float) p1.getY())));
graphicsPath.add(new Ruling(new Point2D.Float((float) p2.getX(), (float) p2.getY()), new Point2D.Float((float) p2.getX(), (float) p1.getY())));
}
if (p2.getY() > p1.getY()) {
graphicsPath.add(new Ruling(new Point2D.Float((float) p1.getX(), (float) p1.getY()), new Point2D.Float((float) p1
.getX(), (float) p2.getY())));
graphicsPath.add(new Ruling(new Point2D.Float((float) p1.getX(), (float) p1.getY()), new Point2D.Float((float) p1.getX(), (float) p2.getY())));
} else {
graphicsPath.add(new Ruling(new Point2D.Float((float) p1.getX(), (float) p2.getY()), new Point2D.Float((float) p1
.getX(), (float) p1.getY())));
graphicsPath.add(new Ruling(new Point2D.Float((float) p1.getX(), (float) p2.getY()), new Point2D.Float((float) p1.getX(), (float) p1.getY())));
}
}
break;
@@ -173,9 +155,6 @@ public class PDFLinesTextStripper extends PDFTextStripper {
graphicsPath.clear();
break;
case OperatorName.DRAW_OBJECT:
processImageOperation(arguments);
break;
}
@@ -183,32 +162,6 @@ public class PDFLinesTextStripper extends PDFTextStripper {
}
protected void processImageOperation(List<COSBase> arguments) {
try {
COSName objectName = (COSName) arguments.get(0);
PDXObject xobject = getResources().getXObject(objectName);
if (xobject instanceof PDImageXObject) {
PDImageXObject image = (PDImageXObject)xobject;
Matrix ctmNew = getGraphicsState().getCurrentTransformationMatrix();
Rectangle2D rect = new Rectangle2D.Float(ctmNew.getTranslateX(), ctmNew.getTranslateY(), ctmNew.getScaleX(), ctmNew.getScaleY());
// Memory Hack - sofReference kills me
FieldUtils.writeField(image, "cachedImageSubsampling", -1, true);
if (rect.getHeight() > 2 && rect.getWidth() > 2) {
this.images.add(new PdfImage(image.getImage(), rect, pageNumber, image.getImage().getColorModel().hasAlpha()));
}
}
} catch (Exception e) {
log.warn("Problem during image extraction: {}", e.getMessage());
}
}
private float floatValue(COSBase value) {
if (value instanceof COSNumber) {
@@ -234,7 +187,7 @@ public class PDFLinesTextStripper extends PDFTextStripper {
rulings.addAll(path);
}
} catch (UnsupportedOperationException e) {
log.error("UnsupportedOperationException: " + getGraphicsState().getStrokingColor()
log.debug("UnsupportedOperationException: " + getGraphicsState().getStrokingColor()
.getColorSpace()
.getName() + " or " + getGraphicsState().getNonStrokingColor()
.getColorSpace()
@@ -247,8 +200,16 @@ public class PDFLinesTextStripper extends PDFTextStripper {
public void writeString(String text, List<TextPosition> textPositions) throws IOException {
int startIndex = 0;
RedTextPosition previous = null;
for (int i = 0; i <= textPositions.size() - 1; i++) {
if (!textPositionSequences.isEmpty()) {
previous = textPositionSequences.get(textPositionSequences.size() - 1)
.getTextPositions()
.get(textPositionSequences.get(textPositionSequences.size() - 1).getTextPositions().size() - 1);
}
int charWidth = (int) textPositions.get(i).getWidthDirAdj();
if (charWidth < minCharWidth) {
minCharWidth = charWidth;
@@ -267,42 +228,58 @@ public class PDFLinesTextStripper extends PDFTextStripper {
if (i == 0 && (textPositions.get(i).getUnicode().equals(" ") || textPositions.get(i)
.getUnicode()
.equals("\u00A0"))) {
.equals("\u00A0") || textPositions.get(i).getUnicode().equals("\t"))) {
startIndex++;
continue;
}
// Strange but sometimes this is happening, for example: Metolachlor2.pdf
if (i > 0 && textPositions.get(i).getX() < textPositions.get(i - 1).getX()) {
if (i > 0 && textPositions.get(i).getXDirAdj() < textPositions.get(i - 1).getXDirAdj()) {
List<TextPosition> sublist = textPositions.subList(startIndex, i);
if (!(sublist.isEmpty() || sublist.size() == 1 && (sublist.get(0)
.getUnicode()
.equals(" ") || sublist.get(0).getUnicode().equals("\u00A0")))) {
.equals(" ") || sublist.get(0).getUnicode().equals("\u00A0") || sublist.get(0)
.getUnicode()
.equals("\t")))) {
textPositionSequences.add(new TextPositionSequence(sublist, pageNumber));
}
startIndex = i;
}
if (textPositions.get(i).getRotation() == 0 && i > 0 && textPositions.get(i).getX() > textPositions.get(i - 1).getEndX() + 1) {
if (textPositions.get(i).getRotation() == 0 && i > 0 && textPositions.get(i)
.getX() > textPositions.get(i - 1).getEndX() + 1) {
List<TextPosition> sublist = textPositions.subList(startIndex, i);
if (!(sublist.isEmpty() || sublist.size() == 1 && (sublist.get(0)
.getUnicode()
.equals(" ") || sublist.get(0).getUnicode().equals("\u00A0")))) {
.equals(" ") || sublist.get(0).getUnicode().equals("\u00A0") || sublist.get(0)
.getUnicode()
.equals("\t")))) {
textPositionSequences.add(new TextPositionSequence(sublist, pageNumber));
}
startIndex = i;
}
if (i > 0 && (textPositions.get(i).getUnicode().equals(" ") || textPositions.get(i)
.getUnicode()
.equals("\u00A0")) && i <= textPositions.size() - 2) {
.equals("\u00A0") || textPositions.get(i)
.getUnicode()
.equals("\t")) && i <= textPositions.size() - 2) {
List<TextPosition> sublist = textPositions.subList(startIndex, i);
if (!(sublist.isEmpty() || sublist.size() == 1 && (sublist.get(0)
.getUnicode()
.equals(" ") || sublist.get(0).getUnicode().equals("\u00A0")))) {
textPositionSequences.add(new TextPositionSequence(sublist, pageNumber));
.equals(" ") || sublist.get(0).getUnicode().equals("\u00A0") || sublist.get(0)
.getUnicode()
.equals("\t")))) {
// Remove false sequence ends (whitespaces)
if (previous != null && sublist.get(0).getYDirAdj() == previous.getYDirAdj() && sublist.get(0)
.getXDirAdj() - (previous.getXDirAdj() + previous.getWidthDirAdj()) < 0.01) {
for (TextPosition t : sublist) {
textPositionSequences.get(textPositionSequences.size() - 1).add(t);
}
} else {
textPositionSequences.add(new TextPositionSequence(sublist, pageNumber));
}
}
startIndex = i + 1;
}
@@ -311,13 +288,23 @@ public class PDFLinesTextStripper extends PDFTextStripper {
List<TextPosition> sublist = textPositions.subList(startIndex, textPositions.size());
if (!sublist.isEmpty() && (sublist.get(sublist.size() - 1)
.getUnicode()
.equals(" ") || sublist.get(sublist.size() - 1).getUnicode().equals("\u00A0"))) {
.equals(" ") || sublist.get(sublist.size() - 1)
.getUnicode()
.equals("\u00A0") || sublist.get(sublist.size() - 1).getUnicode().equals("\t"))) {
sublist = sublist.subList(0, sublist.size() - 1);
}
if (!(sublist.isEmpty() || sublist.size() == 1 && (sublist.get(0).getUnicode().equals(" ") || sublist.get(0)
.getUnicode()
.equals("\u00A0")))) {
textPositionSequences.add(new TextPositionSequence(sublist, pageNumber));
.equals("\u00A0") || sublist.get(0).getUnicode().equals("\t")))) {
if (previous != null && sublist.get(0).getYDirAdj() == previous.getYDirAdj() && sublist.get(0)
.getXDirAdj() - (previous.getXDirAdj() + previous.getWidthDirAdj()) < 0.01) {
for (TextPosition t : sublist) {
textPositionSequences.get(textPositionSequences.size() - 1).add(t);
}
} else {
textPositionSequences.add(new TextPositionSequence(sublist, pageNumber));
}
}
super.writeString(text);
}
@@ -331,7 +318,6 @@ public class PDFLinesTextStripper extends PDFTextStripper {
minCharHeight = Integer.MAX_VALUE;
maxCharHeight = 0;
textPositionSequences.clear();
images = new ArrayList<>();
rulings.clear();
graphicsPath.clear();
path_x = 0.0f;
@@ -1,14 +1,24 @@
package com.iqser.red.service.redaction.v1.server.parsing.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.apache.pdfbox.text.TextPosition;
import org.springframework.beans.BeanUtils;
import com.dslplatform.json.CompiledJson;
import com.dslplatform.json.JsonAttribute;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@CompiledJson
public class RedTextPosition {
private String textMatrix;
@@ -17,21 +27,32 @@ public class RedTextPosition {
private float pageHeight;
private float pageWidth;
private String unicode;
@JsonAlias("xdirAdj")
@JsonAttribute(alternativeNames = {"xdirAdj"})
private float XDirAdj;
@JsonAlias("ydirAdj")
@JsonAttribute(alternativeNames = {"ydirAdj"})
private float YDirAdj;
private float width;
private float heightDir;
private float widthDirAdj;
private float dir;
// not used in reanalysis
@JsonIgnore
@JsonAttribute(ignore = true)
private float widthOfSpace;
// not used in reanalysis
@JsonIgnore
@JsonAttribute(ignore = true)
private float fontSizeInPt;
// not used in reanalysis
@JsonIgnore
@JsonAttribute(ignore = true)
private String fontName;
@@ -49,4 +70,14 @@ public class RedTextPosition {
}
@JsonAlias("xdirAdj")
@JsonAttribute(alternativeNames = {"xdirAdj"})
public void setXDirAdj(float XDirAdj) {this.XDirAdj = XDirAdj;}
@JsonAlias("ydirAdj")
@JsonAttribute(alternativeNames = {"ydirAdj"})
public void setYDirAdj(float YDirAdj) {this.YDirAdj = YDirAdj;}
}
@@ -1,20 +1,31 @@
package com.iqser.red.service.redaction.v1.server.parsing.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.iqser.red.service.redaction.v1.model.Point;
import com.iqser.red.service.redaction.v1.model.Rectangle;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.pdfbox.text.TextPosition;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.pdfbox.text.TextPosition;
import com.dslplatform.json.CompiledJson;
import com.dslplatform.json.JsonAttribute;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.iqser.red.service.redaction.v1.model.Point;
import com.iqser.red.service.redaction.v1.model.Rectangle;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Data
@Builder
@CompiledJson
@NoArgsConstructor
@JsonIgnoreProperties({ "empty" })
@AllArgsConstructor
@JsonIgnoreProperties({"empty"})
public class TextPositionSequence implements CharSequence {
private int page;
@@ -23,12 +34,15 @@ public class TextPositionSequence implements CharSequence {
private float x1;
private float x2;
public TextPositionSequence(int page) {
this.page = page;
}
public static TextPositionSequence fromData(List<RedTextPosition> textPositions, int page) {
var textPositionSequence = new TextPositionSequence();
textPositionSequence.textPositions = textPositions;
textPositionSequence.page = page;
@@ -44,9 +58,6 @@ public class TextPositionSequence implements CharSequence {
}
@Override
public int length() {
@@ -108,6 +119,7 @@ public class TextPositionSequence implements CharSequence {
@JsonIgnore
@JsonAttribute(ignore = true)
public float getX1() {
if (textPositions.get(0).getRotation() == 90) {
@@ -119,17 +131,19 @@ public class TextPositionSequence implements CharSequence {
@JsonIgnore
@JsonAttribute(ignore = true)
public float getX2() {
if (textPositions.get(0).getRotation() == 90) {
return textPositions.get(0).getYDirAdj();
} else {
return textPositions.get(textPositions.size() - 1)
.getXDirAdj() + textPositions.get(textPositions.size() - 1).getWidth() + 1;
return textPositions.get(textPositions.size() - 1).getXDirAdj() + textPositions.get(textPositions.size() - 1).getWidth() + 1;
}
}
@JsonIgnore
@JsonAttribute(ignore = true)
public float getRotationAdjustedY() {
return textPositions.get(0).getY();
@@ -137,6 +151,15 @@ public class TextPositionSequence implements CharSequence {
@JsonIgnore
@JsonAttribute(ignore = true)
public float getRotationAdjustedX() {
return textPositions.get(0).getXDirAdj();
}
@JsonIgnore
@JsonAttribute(ignore = true)
public float getY1() {
if (textPositions.get(0).getRotation() == 90) {
@@ -148,6 +171,7 @@ public class TextPositionSequence implements CharSequence {
@JsonIgnore
@JsonAttribute(ignore = true)
public float getY2() {
if (textPositions.get(0).getRotation() == 90) {
@@ -159,6 +183,7 @@ public class TextPositionSequence implements CharSequence {
@JsonIgnore
@JsonAttribute(ignore = true)
public float getTextHeight() {
return textPositions.get(0).getHeightDir() + 2;
@@ -166,6 +191,7 @@ public class TextPositionSequence implements CharSequence {
@JsonIgnore
@JsonAttribute(ignore = true)
public float getHeight() {
return getY2() - getY1();
@@ -173,6 +199,7 @@ public class TextPositionSequence implements CharSequence {
@JsonIgnore
@JsonAttribute(ignore = true)
public float getWidth() {
return getX2() - getX1();
@@ -180,15 +207,15 @@ public class TextPositionSequence implements CharSequence {
@JsonIgnore
@JsonAttribute(ignore = true)
public String getFont() {
return textPositions.get(0).getFontName()
.toLowerCase()
.replaceAll(",bold", "")
.replaceAll(",italic", "");
return textPositions.get(0).getFontName().toLowerCase().replaceAll(",bold", "").replaceAll(",italic", "");
}
@JsonIgnore
@JsonAttribute(ignore = true)
public String getFontStyle() {
String lowercaseFontName = textPositions.get(0).getFontName().toLowerCase();
@@ -205,27 +232,37 @@ public class TextPositionSequence implements CharSequence {
}
@JsonIgnore
@JsonAttribute(ignore = true)
public float getFontSize() {
return textPositions.get(0).getFontSizeInPt();
}
@JsonIgnore
@JsonAttribute(ignore = true)
public float getSpaceWidth() {
return textPositions.get(0).getWidthOfSpace();
}
@JsonIgnore
@JsonAttribute(ignore = true)
public int getRotation() {
return textPositions.get(0).getRotation();
}
@JsonIgnore
@JsonAttribute(ignore = true)
public Rectangle getRectangle() {
log.debug("Page: '{}', Word: '{}', Rotation: '{}', textRotation {}", page, this, textPositions.get(0).getRotation(), textPositions.get(0).getDir());
float height = getTextHeight();
float posXInit = getX1();
@@ -233,26 +270,118 @@ public class TextPositionSequence implements CharSequence {
float posYInit;
float posYEnd;
if (textPositions.get(0).getRotation() == 90) {
if (textPositions.get(0).getRotation() == 0 && textPositions.get(0).getDir() == 90f) {
posYInit = getX1();
posYEnd = getX2() + textPositions.get(0).getWidthDirAdj() - textPositions.get(textPositions.size() - 1).getWidthDirAdj() - 3;
posXInit = textPositions.get(0).getYDirAdj() + 2;
posXEnd = textPositions.get(textPositions.size() - 1).getYDirAdj() - height;
} else if (textPositions.get(0).getRotation() == 0 && textPositions.get(0).getDir() == 180f) {
posXInit = textPositions.get(0).getPageWidth() - getX1() + 1;
posXEnd = textPositions.get(0).getPageWidth() - getX2() + textPositions.get(0).getWidthDirAdj() - textPositions.get(textPositions.size() - 1).getWidthDirAdj() - 3;
posYInit = textPositions.get(0).getYDirAdj() - height + 2;
posYEnd = textPositions.get(textPositions.size() - 1).getYDirAdj() - height + 2;
} else if (textPositions.get(0).getRotation() == 0 && textPositions.get(0).getDir() == 270f) {
posYInit = textPositions.get(0).getPageHeight() - getX1();
posYEnd = textPositions.get(0).getPageHeight() - getX2() - textPositions.get(0).getWidthDirAdj() - textPositions.get(textPositions.size() - 1).getWidthDirAdj() - 3;
posXInit = textPositions.get(0).getPageWidth() - textPositions.get(0).getYDirAdj() - 2;
posXEnd = textPositions.get(0).getPageWidth() - textPositions.get(textPositions.size() - 1).getYDirAdj() + height;
} else if (textPositions.get(0).getRotation() == 90 && textPositions.get(0).getDir() == 0.0f) {
posXInit = textPositions.get(textPositions.size() - 1).getXDirAdj() + textPositions.get(textPositions.size() - 1).getHeightDir();
posXEnd = textPositions.get(0).getXDirAdj();
posYInit = textPositions.get(0).getPageHeight() - textPositions.get(0).getYDirAdj() - 2;
posYEnd = textPositions.get(0).getPageHeight() - textPositions.get(textPositions.size() - 1).getYDirAdj() + 2;
} else if (textPositions.get(0).getRotation() == 90 && textPositions.get(0).getDir() == 90.0f) {
posXEnd = textPositions.get(0).getYDirAdj() + 2;
posYInit = getY1();
posYEnd = textPositions.get(textPositions.size() - 1).getXDirAdj() - height + 4;
} else if (textPositions.get(0).getRotation() == 270) {
posYInit = textPositions.get(0).getPageHeight() - getX1();
posYEnd = textPositions.get(0).getPageHeight() - getX2() - textPositions.get(0)
.getWidth() - textPositions.get(textPositions.size() - 1).getWidth() - 1;
posXInit = textPositions.get(0).getPageWidth() - textPositions.get(0).getYDirAdj() - 2;
posXEnd = textPositions.get(0).getPageWidth() - textPositions.get(textPositions.size() - 1)
.getYDirAdj() + height;
} else {
posXEnd = textPositions.get(textPositions.size() - 1)
.getXDirAdj() + textPositions.get(textPositions.size() - 1).getWidth() + 1;
} else if (textPositions.get(0).getRotation() == 90 && textPositions.get(0).getDir() == 180.0f) {
posXInit = textPositions.get(0).getPageWidth() - textPositions.get(textPositions.size() - 1).getXDirAdj() - 4;
posXEnd = textPositions.get(0).getPageWidth() - textPositions.get(0).getXDirAdj();
posYInit = textPositions.get(0).getYDirAdj() - 2 - textPositions.get(textPositions.size() - 1).getHeightDir();
posYEnd = textPositions.get(textPositions.size() - 1).getYDirAdj() - textPositions.get(textPositions.size() - 1).getHeightDir();
} else if (textPositions.get(0).getRotation() == 90 && textPositions.get(0).getDir() == 270.0f) {
posXInit = textPositions.get(0).getPageWidth() - getX1();
posXEnd = textPositions.get(0).getPageWidth() - textPositions.get(0).getYDirAdj() - 2;
posYInit = textPositions.get(0).getPageHeight() - getY1();
posYEnd = textPositions.get(0).getPageHeight() - textPositions.get(textPositions.size() - 1).getXDirAdj() - height - 4;
} else if (textPositions.get(0).getRotation() == 180 && textPositions.get(0).getDir() == 0f) {
posXEnd = textPositions.get(textPositions.size() - 1).getXDirAdj() + textPositions.get(textPositions.size() - 1).getWidthDirAdj() + 1;
posYInit = textPositions.get(0).getPageHeight() - textPositions.get(0).getYDirAdj() - 2;
posYEnd = textPositions.get(0).getPageHeight() - textPositions.get(textPositions.size() - 1)
.getYDirAdj() + 2;
posYEnd = textPositions.get(0).getPageHeight() - textPositions.get(textPositions.size() - 1).getYDirAdj() + 2;
} else if (textPositions.get(0).getRotation() == 180 && textPositions.get(0).getDir() == 90f) {
posYInit = getX1();
posYEnd = getX2() - 3;
posXInit = textPositions.get(0).getYDirAdj() + 2;
posXEnd = textPositions.get(textPositions.size() - 1).getYDirAdj() - height;
} else if (textPositions.get(0).getRotation() == 180 && textPositions.get(0).getDir() == 180f) {
posXInit = textPositions.get(0).getPageWidth() - getX1() + 1;
posXEnd = textPositions.get(0).getPageWidth() - getX2() + textPositions.get(0).getWidthDirAdj() - textPositions.get(textPositions.size() - 1).getWidthDirAdj() - 3;
posYInit = textPositions.get(0).getYDirAdj() - height + 2;
posYEnd = textPositions.get(textPositions.size() - 1).getYDirAdj() - height + 2;
} else if (textPositions.get(0).getRotation() == 180 && textPositions.get(0).getDir() == 270.0f) {
posYInit = textPositions.get(0).getPageHeight() - getX1();
posYEnd = textPositions.get(0).getPageHeight() - getX2() - textPositions.get(0).getWidthDirAdj() - textPositions.get(textPositions.size() - 1).getWidthDirAdj();
posXInit = textPositions.get(0).getPageWidth() - textPositions.get(0).getYDirAdj() - 2;
posXEnd = textPositions.get(0).getPageWidth() - textPositions.get(textPositions.size() - 1).getYDirAdj() + height;
} else if (textPositions.get(0).getRotation() == 270 && textPositions.get(0).getDir() == 0.0f) {
posYInit = textPositions.get(0).getPageHeight() - textPositions.get(0).getYDirAdj() - 2;
posYEnd = posYInit + 1;
posXInit = textPositions.get(0).getXDirAdj();
posXEnd = textPositions.get(textPositions.size() - 1).getXDirAdj() + textPositions.get(textPositions.size() - 1).getWidthDirAdj() + 0.1f;
} else if (textPositions.get(0).getRotation() == 270 && textPositions.get(0).getDir() == 90.0f) {
posYInit = getX1();
posYEnd = getX2() - height;
posXInit = textPositions.get(0).getYDirAdj() + 2;
posXEnd = textPositions.get(textPositions.size() - 1).getYDirAdj() - height;
} else if (textPositions.get(0).getRotation() == 270 && textPositions.get(0).getDir() == 180.0f) {
posXInit = textPositions.get(0).getPageWidth() - getX1() + 1;
posXEnd = textPositions.get(0).getPageWidth() - getX2() - 4;
posYInit = textPositions.get(0).getYDirAdj() - height + 2;
posYEnd = textPositions.get(textPositions.size() - 1).getYDirAdj() - height + 2;
} else if (textPositions.get(0).getRotation() == 270 && textPositions.get(0).getDir() == 270.0f) {
posYInit = textPositions.get(0).getPageHeight() - getX1();
posYEnd = textPositions.get(0).getPageHeight() - getX2() - height;
posXInit = textPositions.get(0).getPageWidth() - textPositions.get(0).getYDirAdj() - 2;
posXEnd = textPositions.get(0).getPageWidth() - textPositions.get(textPositions.size() - 1).getYDirAdj() + height;
} else {
// page rotation = 0 and text direction = 0
posXEnd = textPositions.get(textPositions.size() - 1).getXDirAdj() + textPositions.get(textPositions.size() - 1).getWidthDirAdj() + 1;
posYInit = textPositions.get(0).getPageHeight() - textPositions.get(0).getYDirAdj() - 2;
posYEnd = textPositions.get(0).getPageHeight() - textPositions.get(textPositions.size() - 1).getYDirAdj() + 2;
}
return new Rectangle(new Point(posXInit, posYInit), posXEnd - posXInit, posYEnd - posYInit + height, page);
var rectangle = new Rectangle(new Point(posXInit, posYInit), posXEnd - posXInit, posYEnd - posYInit + height, page);
log.debug("Rectangle: {}", rectangle);
return rectangle;
}
}
@@ -0,0 +1,25 @@
package com.iqser.red.service.redaction.v1.server.queue;
import static com.iqser.red.service.redaction.v1.server.queue.MessagingConfiguration.REDACTION_QUEUE;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequiredArgsConstructor
public class MessageReceiver {
private final RedactionMessageReceiver redactionMessageReceiver;
@RabbitHandler
@RabbitListener(queues = REDACTION_QUEUE)
public void receiveAnalyzeRequest(String in) throws JsonProcessingException {
redactionMessageReceiver.receiveAnalyzeRequest(in, false);
}
}
@@ -1,8 +1,10 @@
package com.iqser.red.service.redaction.v1.server.queue;
import lombok.RequiredArgsConstructor;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -10,11 +12,24 @@ import org.springframework.context.annotation.Configuration;
@RequiredArgsConstructor
public class MessagingConfiguration {
public static final String REDACTION_QUEUE = "redactionQueue";
public static final String REDACTION_DQL = "redactionDQL";
public static final String REDACTION_PRIORITY_QUEUE = "redactionPriorityQueue";
@Bean
@ConditionalOnProperty(prefix = "redaction-service", name = "priorityMode", havingValue = "false")
public MessageReceiver messageReceiver(RedactionMessageReceiver redactionMessageReceiver){
return new MessageReceiver(redactionMessageReceiver);
}
@Bean
@ConditionalOnProperty(prefix = "redaction-service", name = "priorityMode", havingValue = "true")
public PriorityMessageReceiver priorityMessageReceiver(RedactionMessageReceiver redactionMessageReceiver){
return new PriorityMessageReceiver(redactionMessageReceiver);
}
@Bean
public Queue redactionQueue() {
@@ -27,9 +42,21 @@ public class MessagingConfiguration {
}
@Bean
public Queue redactionPriorityQueue() {
return QueueBuilder.durable(REDACTION_PRIORITY_QUEUE)
.withArgument("x-dead-letter-exchange", "")
.withArgument("x-dead-letter-routing-key", REDACTION_DQL)
.maxPriority(2)
.build();
}
@Bean
public Queue redactionDeadLetterQueue() {
return QueueBuilder.durable(REDACTION_DQL).build();
}
}
@@ -0,0 +1,27 @@
package com.iqser.red.service.redaction.v1.server.queue;
import static com.iqser.red.service.redaction.v1.server.queue.MessagingConfiguration.REDACTION_PRIORITY_QUEUE;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequiredArgsConstructor
public class PriorityMessageReceiver {
private final RedactionMessageReceiver redactionMessageReceiver;
@RabbitHandler
@RabbitListener(queues = REDACTION_PRIORITY_QUEUE)
public void receiveAnalyzeRequest(String in) throws JsonProcessingException {
redactionMessageReceiver.receiveAnalyzeRequest(in, true);
}
}
@@ -1,5 +1,11 @@
package com.iqser.red.service.redaction.v1.server.queue;
import static com.iqser.red.service.redaction.v1.server.queue.MessagingConfiguration.REDACTION_DQL;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iqser.red.service.redaction.v1.model.AnalyzeRequest;
@@ -7,18 +13,11 @@ import com.iqser.red.service.redaction.v1.model.AnalyzeResult;
import com.iqser.red.service.redaction.v1.model.StructureAnalyzeRequest;
import com.iqser.red.service.redaction.v1.server.client.FileStatusProcessingUpdateClient;
import com.iqser.red.service.redaction.v1.server.redaction.service.AnalyzeService;
import com.iqser.red.service.redaction.v1.server.redaction.service.NerAnalyserService;
import com.iqser.red.service.redaction.v1.server.redaction.service.ManualRedactionSurroundingTextService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
import static com.iqser.red.service.redaction.v1.server.queue.MessagingConfiguration.REDACTION_DQL;
import static com.iqser.red.service.redaction.v1.server.queue.MessagingConfiguration.REDACTION_QUEUE;
@Slf4j
@Service
@RequiredArgsConstructor
@@ -27,32 +26,49 @@ public class RedactionMessageReceiver {
private final ObjectMapper objectMapper;
private final AnalyzeService analyzeService;
private final FileStatusProcessingUpdateClient fileStatusProcessingUpdateClient;
private final NerAnalyserService nerAnalyserService;
private final ManualRedactionSurroundingTextService manualRedactionSurroundingTextService;
@RabbitHandler
@RabbitListener(queues = REDACTION_QUEUE)
public void receiveAnalyzeRequest(String in) throws JsonProcessingException {
public void receiveAnalyzeRequest(String in, boolean priority) throws JsonProcessingException {
var analyzeRequest = objectMapper.readValue(in, AnalyzeRequest.class);
log.info("Processing analyze request for file: {}", analyzeRequest.getFileId());
AnalyzeResult result;
if (analyzeRequest.isReanalyseOnlyIfPossible()) {
result = analyzeService.reanalyze(analyzeRequest);
log.info("Successfully reanalyzed dossier {} file {} took: {}", analyzeRequest.getDossierId(), analyzeRequest.getFileId(), result.getDuration());
} else {
// TODO Seperate stucture analysis by other queue
analyzeService.analyzeDocumentStructure(new StructureAnalyzeRequest(analyzeRequest.getDossierId(), analyzeRequest.getFileId()));
log.info("Processing priority: {} analyze request for file: {}", priority, analyzeRequest.getFileId());
AnalyzeResult result = null;
// TODO NerEntities should be computed and stored in entity-recognition-service, should be triggered by a seperate queue after structure analysis
nerAnalyserService.computeNerEntities(analyzeRequest.getDossierId(), analyzeRequest.getFileId());
try {
switch (analyzeRequest.getMessageType()) {
result = analyzeService.analyze(analyzeRequest);
log.info("Successfully analyzed dossier {} file {} took: {}", analyzeRequest.getDossierId(), analyzeRequest.getFileId(), result
.getDuration());
case REANALYSE:
result = analyzeService.reanalyze(analyzeRequest);
log.info("Successfully reanalyzed dossier {} file {} took: {}", analyzeRequest.getDossierId(), analyzeRequest.getFileId(), result.getDuration());
break;
case STRUCTURE_ANALYSE:
result = analyzeService.analyzeDocumentStructure(new StructureAnalyzeRequest(analyzeRequest.getDossierId(), analyzeRequest.getFileId()));
log.info("Successfully analyzed structure dossier {} file {} took: {}", analyzeRequest.getDossierId(), analyzeRequest.getFileId(), result.getDuration());
break;
case ANALYSE:
result = analyzeService.analyze(analyzeRequest);
log.info("Successfully analyzed dossier {} file {} took: {}", analyzeRequest.getDossierId(), analyzeRequest.getFileId(), result.getDuration());
break;
case SURROUNDING_TEXT:
result = manualRedactionSurroundingTextService.addSurroundingText(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), analyzeRequest.getManualRedactions());
log.info("Successfully added surrounding text for manual redaction in dossierId {} and fileId {} took: {}", analyzeRequest.getDossierId(), analyzeRequest.getFileId(), result.getDuration());
break;
default:
throw new IllegalArgumentException("Unknown MessageType: " + analyzeRequest.getMessageType());
}
result.setMessageType(analyzeRequest.getMessageType());
fileStatusProcessingUpdateClient.analysisSuccessful(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), result);
} catch (Exception e) {
log.warn("Failed to process analyze request: {}", analyzeRequest, e);
fileStatusProcessingUpdateClient.analysisFailed(analyzeRequest.getDossierId(), analyzeRequest.getFileId());
}
fileStatusProcessingUpdateClient.analysisSuccessful(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), result);
}
@@ -1,22 +1,16 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import static java.util.stream.Collectors.toSet;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.type.DictionaryEntry;
import lombok.Data;
import lombok.Getter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.Data;
import lombok.Getter;
@Data
public class Dictionary {
public static final String RECOMMENDATION_PREFIX = "recommendation_";
@Getter
private List<DictionaryModel> dictionaryModels;
private Map<String, DictionaryModel> localAccessMap = new HashMap<>();
@@ -26,6 +20,7 @@ public class Dictionary {
public Dictionary(List<DictionaryModel> dictionaryModels, DictionaryVersion version) {
this.dictionaryModels = dictionaryModels;
this.dictionaryModels.forEach(dm -> localAccessMap.put(dm.getType(), dm));
this.version = version;
@@ -33,6 +28,7 @@ public class Dictionary {
public int getDictionaryRank(String type) {
if (!localAccessMap.containsKey(type)) {
return 0;
}
@@ -40,16 +36,6 @@ public class Dictionary {
}
public boolean isRecommendation(String type) {
DictionaryModel model = localAccessMap.get(type);
if (model != null) {
return model.isRecommendation();
}
return false;
}
public boolean hasLocalEntries() {
return dictionaryModels.stream().anyMatch(dm -> !dm.getLocalEntries().isEmpty());
@@ -62,17 +48,9 @@ public class Dictionary {
}
public boolean containsValue(String type, String value) {
public DictionaryModel getType(String type) {
return localAccessMap.containsKey(type) && localAccessMap.get(type)
.getEntries().stream().map(DictionaryEntry::getValue).collect(toSet())
.contains(value) || localAccessMap.containsKey(type) && localAccessMap.get(type)
.getLocalEntries()
.contains(value) || localAccessMap.containsKey(RECOMMENDATION_PREFIX + type) && localAccessMap.get(RECOMMENDATION_PREFIX + type)
.getEntries().stream().map(DictionaryEntry::getValue).collect(toSet())
.contains(value) || localAccessMap.containsKey(RECOMMENDATION_PREFIX + type) && localAccessMap.get(RECOMMENDATION_PREFIX + type)
.getLocalEntries()
.contains(value);
return localAccessMap.get(type);
}
@@ -85,6 +63,7 @@ public class Dictionary {
return false;
}
public boolean isCaseInsensitiveDictionary(String type) {
DictionaryModel dictionaryModel = localAccessMap.get(type);
@@ -0,0 +1,26 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import java.util.HashSet;
import java.util.Set;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.type.DictionaryEntry;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DictionaryEntries {
@Builder.Default
Set<DictionaryEntry> entries = new HashSet<>();
@Builder.Default
Set<DictionaryEntry> falsePositives = new HashSet<>();
@Builder.Default
Set<DictionaryEntry> falseRecommendations = new HashSet<>();
}
@@ -2,10 +2,12 @@ package com.iqser.red.service.redaction.v1.server.redaction.model;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.type.DictionaryEntry;
import com.iqser.red.service.redaction.v1.server.redaction.utils.SearchImplementation;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
@@ -14,19 +16,76 @@ import java.util.stream.Collectors;
@AllArgsConstructor
public class DictionaryModel implements Serializable {
private String type;
private int rank;
private float[] color;
private boolean caseInsensitive;
private boolean hint;
private boolean recommendation;
private Set<DictionaryEntry> entries;
private Set<String> localEntries;
private boolean isDossierDictionary;
private final String type;
private final int rank;
private final float[] color;
private final boolean caseInsensitive;
private final boolean hint;
private final boolean isDossierDictionary;
private final Set<DictionaryEntry> entries;
private final Set<DictionaryEntry> falsePositives;
private final Set<DictionaryEntry> falseRecommendations;
private transient SearchImplementation entriesSearch;
private transient SearchImplementation falsePositiveSearch;
private transient SearchImplementation falseRecommendationsSearch;
private transient SearchImplementation localSearch;
private final Set<String> localEntries = new HashSet<>();
public DictionaryModel(String type,
int rank,
float[] color,
boolean caseInsensitive,
boolean hint,
Set<DictionaryEntry> entries,
Set<DictionaryEntry> falsePositives,
Set<DictionaryEntry> falseRecommendations,
boolean isDossierDictionary) {
this.type = type;
this.rank = rank;
this.color = color;
this.caseInsensitive = caseInsensitive;
this.hint = hint;
this.isDossierDictionary = isDossierDictionary;
this.entries = entries;
this.falsePositives = falsePositives;
this.falseRecommendations = falseRecommendations;
this.entriesSearch = new SearchImplementation(this.entries.stream().filter(e -> !e.isDeleted()).map(DictionaryEntry::getValue).collect(Collectors.toList()), caseInsensitive);
this.falsePositiveSearch = new SearchImplementation(this.falsePositives.stream().filter(e -> !e.isDeleted()).map(DictionaryEntry::getValue).collect(Collectors.toList()), caseInsensitive);
this.falseRecommendationsSearch = new SearchImplementation(this.falseRecommendations.stream().filter(e -> !e.isDeleted()).map(DictionaryEntry::getValue).collect(Collectors.toList()), caseInsensitive);
public Set<String> getValues(boolean local) {
return local ? localEntries : entries.stream().filter(e -> !e.isDeleted()).map(e -> e.getValue()).collect(Collectors
.toSet());
}
public SearchImplementation getLocalSearch() {
if (this.localSearch == null) {
this.localSearch = new SearchImplementation(this.localEntries, caseInsensitive);
}
return this.localSearch;
}
public SearchImplementation getEntriesSearch() {
if (entriesSearch == null) {
this.entriesSearch = new SearchImplementation(this.entries.stream().filter(e -> !e.isDeleted()).map(DictionaryEntry::getValue).collect(Collectors.toList()), caseInsensitive);
}
return entriesSearch;
}
public SearchImplementation getFalsePositiveSearch() {
if (falsePositiveSearch == null) {
this.falsePositiveSearch = new SearchImplementation(this.falsePositives.stream().filter(e -> !e.isDeleted()).map(DictionaryEntry::getValue).collect(Collectors.toList()), caseInsensitive);
}
return falsePositiveSearch;
}
public SearchImplementation getFalseRecommendationsSearch() {
if (falseRecommendationsSearch == null) {
this.falseRecommendationsSearch = new SearchImplementation(this.falseRecommendations.stream().filter(e -> !e.isDeleted()).map(DictionaryEntry::getValue).collect(Collectors.toList()), caseInsensitive);
}
return falseRecommendationsSearch;
}
}
@@ -0,0 +1,23 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import java.util.HashSet;
import java.util.Set;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Entities {
@Builder.Default
private Set<Entity> entities = new HashSet<>();
@Builder.Default
private Set<Entity> nerEntities = new HashSet<>();
}
@@ -1,22 +1,28 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import com.iqser.red.service.redaction.v1.model.Engine;
import com.iqser.red.service.redaction.v1.server.parsing.model.TextPositionSequence;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.iqser.red.service.redaction.v1.model.Engine;
import com.iqser.red.service.redaction.v1.server.parsing.model.TextPositionSequence;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Entity implements ReasonHolder {
private final String word;
private final String type;
private String word;
private String type;
private boolean redaction;
private String redactionReason;
private String legalBasis;
@@ -42,15 +48,21 @@ public class Entity implements ReasonHolder {
private boolean isDossierDictionaryEntry;
private boolean ignored;
private Set<Engine> engines = new HashSet<>();
private Set<Entity> references = new HashSet<>();
private EntityType entityType;
private boolean resized;
public Entity(String word, String type, boolean redaction, String redactionReason,
List<EntityPositionSequence> positionSequences, String headline, int matchedRule, int sectionNumber,
String legalBasis, boolean isDictionaryEntry, String textBefore, String textAfter, Integer start,
Integer end, boolean isDossierDictionaryEntry, Set<Engine> engines, Set<Entity> references) {
Integer end, boolean isDossierDictionaryEntry, Set<Engine> engines, Set<Entity> references, EntityType entityType) {
this.word = word;
this.type = type;
@@ -69,11 +81,12 @@ public class Entity implements ReasonHolder {
this.isDossierDictionaryEntry = isDossierDictionaryEntry;
this.engines = engines;
this.references = references;
this.entityType = entityType;
}
public Entity(String word, String type, Integer start, Integer end, String headline, int sectionNumber,
boolean isDictionaryEntry, boolean isDossierDictionaryEntry, Engine engine) {
boolean isDictionaryEntry, boolean isDossierDictionaryEntry, Engine engine, EntityType entityType) {
this.word = word;
this.type = type;
@@ -84,6 +97,7 @@ public class Entity implements ReasonHolder {
this.isDictionaryEntry = isDictionaryEntry;
this.isDossierDictionaryEntry = isDossierDictionaryEntry;
this.engines.add(engine);
this.entityType = entityType;
}
@@ -1,15 +1,18 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import com.iqser.red.service.redaction.v1.server.parsing.model.TextPositionSequence;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import java.util.ArrayList;
import java.util.List;
import com.iqser.red.service.redaction.v1.server.parsing.model.TextPositionSequence;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
@Data
@Builder
@RequiredArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@@ -0,0 +1,5 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
public enum EntityType {
ENTITY, RECOMMENDATION, FALSE_POSITIVE, FALSE_RECOMMENDATION
}
@@ -1,5 +1,6 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import com.dslplatform.json.CompiledJson;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -7,6 +8,7 @@ import lombok.NoArgsConstructor;
@Data
@Builder
@CompiledJson
@NoArgsConstructor
@AllArgsConstructor
public class Image implements ReasonHolder {
@@ -20,6 +22,7 @@ public class Image implements ReasonHolder {
private int sectionNumber;
private String section;
private int page;
private boolean ignored;
private boolean hasTransparency;
}
@@ -1,33 +1,21 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
@Data
@RequiredArgsConstructor
public class PdfImage {
@JsonIgnore
private BufferedImage image;
@NonNull
private RedRectangle2D position;
@NonNull
private ImageType imageType;
private boolean isAppendedToParagraph;
@NonNull
private boolean hasTransparency;
@NonNull
private int page;
public PdfImage(BufferedImage image, Rectangle2D position, int page, boolean hasTransparency) {
this.image = image;
this.position = new RedRectangle2D(position.getX(), position.getY(), position.getWidth(), position.getHeight());
this.page = page;
this.hasTransparency = hasTransparency;
}
}
@@ -1,12 +1,17 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import com.dslplatform.json.CompiledJson;
import com.dslplatform.json.JsonAttribute;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@CompiledJson
@NoArgsConstructor
@AllArgsConstructor
public class RedRectangle2D {
@@ -19,6 +24,7 @@ public class RedRectangle2D {
private double height;
@JsonIgnore
@JsonAttribute(ignore = true)
public boolean isEmpty() {
return width <= 0.0f || height <= 0.0f;
}
@@ -1,16 +1,22 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import com.dslplatform.json.JsonAttribute;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.iqser.red.service.redaction.v1.server.parsing.model.TextPositionSequence;
import com.iqser.red.service.redaction.v1.server.redaction.utils.IdBuilder;
import com.iqser.red.service.redaction.v1.server.redaction.utils.SeparatorUtils;
import com.iqser.red.service.redaction.v1.server.redaction.utils.TextNormalizationUtilities;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
public class SearchableText {
@JsonIgnore
@JsonAttribute(ignore = true)
private transient String stringRepresentation;
private final List<TextPositionSequence> sequences = new ArrayList<>();
@@ -88,8 +94,8 @@ public class SearchableText {
.get(i)
.charAt(j, caseInsensitive) == '-') {
if (counter != 0 || i == 0 && j == 0 || j != 0 && isSeparator(searchSpace.get(i)
.charAt(j - 1, caseInsensitive)) || j == 0 && i != 0 && isSeparator(searchSpace.get(i - 1)
if (counter != 0 || i == 0 && j == 0 || j != 0 && SeparatorUtils.isSeparator(searchSpace.get(i)
.charAt(j - 1, caseInsensitive)) || j == 0 && i != 0 && SeparatorUtils.isSeparator(searchSpace.get(i - 1)
.charAt(searchSpace.get(i - 1)
.length() - 1, caseInsensitive)) || j == 0 && i != 0 && searchSpace.get(i - 1)
.charAt(searchSpace.get(i - 1).length() - 1, caseInsensitive) != ' ' && searchSpace.get(i)
@@ -105,9 +111,9 @@ public class SearchableText {
crossSequenceParts.add(partMatch);
if (i == searchSpace.size() - 1 && j == searchSpace.get(i)
.length() - 1 || j != searchSpace.get(i).length() - 1 && isSeparator(searchSpace.get(i)
.length() - 1 || j != searchSpace.get(i).length() - 1 && SeparatorUtils.isSeparator(searchSpace.get(i)
.charAt(j + 1, caseInsensitive)) || j == searchSpace.get(i)
.length() - 1 && isSeparator(searchSpace.get(i + 1)
.length() - 1 && SeparatorUtils.isSeparator(searchSpace.get(i + 1)
.charAt(0, caseInsensitive)) || j == searchSpace.get(i)
.length() - 1 && searchSpace.get(i)
.charAt(j, caseInsensitive) != ' ' && searchSpace.get(i + 1)
@@ -175,18 +181,18 @@ public class SearchableText {
}
private boolean isSeparator(char c) {
return Character.isWhitespace(c) || Pattern.matches("\\p{Punct}", String.valueOf(c)) || c == '\"' || c == '‘' || c == '’';
}
@Override
public String toString() {
return buildString(sequences);
}
public String asString() {
if (stringRepresentation == null) {
stringRepresentation = buildString(sequences);
}
return stringRepresentation;
}
public String buildString(List<TextPositionSequence> sequences) {
@@ -1,5 +1,7 @@
package com.iqser.red.service.redaction.v1.server.redaction.model;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -9,5 +11,6 @@ public class SectionSearchableTextPair {
private Section section;
private SearchableText searchableText;
private List<Integer> cellStarts;
}
@@ -0,0 +1,16 @@
package com.iqser.red.service.redaction.v1.server.redaction.model.image;
import java.util.HashMap;
import java.util.Map;
import com.dslplatform.json.CompiledJson;
import lombok.Data;
@Data
@CompiledJson
public class Classification {
private Map<String, Float> probabilities = new HashMap<>();
private String label;
}
@@ -0,0 +1,12 @@
package com.iqser.red.service.redaction.v1.server.redaction.model.image;
import com.dslplatform.json.CompiledJson;
import lombok.Data;
@Data
@CompiledJson
public class FilterGeometry {
private ImageSize imageSize;
private ImageFormat imageFormat;
}
@@ -0,0 +1,13 @@
package com.iqser.red.service.redaction.v1.server.redaction.model.image;
import com.dslplatform.json.CompiledJson;
import lombok.Data;
@Data
@CompiledJson
public class Filters {
private FilterGeometry geometry;
private Probability probability;
private boolean allPassed;
}
@@ -0,0 +1,11 @@
package com.iqser.red.service.redaction.v1.server.redaction.model.image;
import com.dslplatform.json.CompiledJson;
import lombok.Data;
@Data
@CompiledJson
public class Geometry {
private float width;
private float height;
}
@@ -0,0 +1,14 @@
package com.iqser.red.service.redaction.v1.server.redaction.model.image;
import com.dslplatform.json.CompiledJson;
import lombok.Data;
@Data
@CompiledJson
public class ImageFormat {
private float quotient;
private boolean tooTall;
private boolean tooWide;
}
@@ -0,0 +1,15 @@
package com.iqser.red.service.redaction.v1.server.redaction.model.image;
import com.dslplatform.json.CompiledJson;
import lombok.Data;
@Data
@CompiledJson
public class ImageMetadata {
private Classification classification;
private Position position;
private Geometry geometry;
private Filters filters;
private boolean alpha;
}
@@ -0,0 +1,29 @@
package com.iqser.red.service.redaction.v1.server.redaction.model.image;
import com.dslplatform.json.CompiledJson;
import com.dslplatform.json.JsonAttribute;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
@CompiledJson
public class ImageServiceResponse {
private String dossierId;
private String fileId;
@JsonProperty(value = "imageMetadata")
@JsonAlias("data")
@JsonAttribute(alternativeNames = {"imageMetadata"})
private List<ImageMetadata> data = new ArrayList<>();
@JsonProperty(value = "imageMetadata")
@JsonAlias("data")
@JsonAttribute(alternativeNames = {"imageMetadata"})
public void setData(List<ImageMetadata> data) {this.data = data;}
}
@@ -0,0 +1,14 @@
package com.iqser.red.service.redaction.v1.server.redaction.model.image;
import com.dslplatform.json.CompiledJson;
import lombok.Data;
@Data
@CompiledJson
public class ImageSize {
private float quotient;
private boolean tooLarge;
private boolean tooSmall;
}
@@ -0,0 +1,14 @@
package com.iqser.red.service.redaction.v1.server.redaction.model.image;
import com.dslplatform.json.CompiledJson;
import lombok.Data;
@Data
@CompiledJson
public class Position {
private float x1;
private float x2;
private float y1;
private float y2;
private int pageNumber;
}
@@ -0,0 +1,12 @@
package com.iqser.red.service.redaction.v1.server.redaction.model.image;
import com.dslplatform.json.CompiledJson;
import lombok.Data;
@Data
@CompiledJson
public class Probability {
private boolean unconfident;
}
@@ -1,41 +1,62 @@
package com.iqser.red.service.redaction.v1.server.redaction.service;
import static com.iqser.red.service.redaction.v1.server.redaction.service.ImportedRedactionService.IMPORTED_REDACTION_TYPE;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.*;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileType;
import org.kie.api.runtime.KieContainer;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.ManualRedactions;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.IdRemoval;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.ManualForceRedaction;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.ManualImageRecategorization;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.ManualLegalBasisChange;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.ManualResizeRedaction;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileType;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.legalbasis.LegalBasis;
import com.iqser.red.service.redaction.v1.model.AnalyzeRequest;
import com.iqser.red.service.redaction.v1.model.AnalyzeResult;
import com.iqser.red.service.redaction.v1.model.Rectangle;
import com.iqser.red.service.redaction.v1.model.RedactionLog;
import com.iqser.red.service.redaction.v1.model.RedactionLogEntry;
import com.iqser.red.service.redaction.v1.model.RedactionLogLegalBasis;
import com.iqser.red.service.redaction.v1.model.SectionArea;
import com.iqser.red.service.redaction.v1.model.SectionGrid;
import com.iqser.red.service.redaction.v1.model.StructureAnalyzeRequest;
import com.iqser.red.service.redaction.v1.server.classification.model.Document;
import com.iqser.red.service.redaction.v1.server.classification.model.SectionText;
import com.iqser.red.service.redaction.v1.server.classification.model.SimplifiedSectionText;
import com.iqser.red.service.redaction.v1.server.classification.model.SimplifiedText;
import com.iqser.red.service.redaction.v1.server.classification.model.Text;
import com.iqser.red.service.redaction.v1.server.client.LegalBasisClient;
import com.iqser.red.service.redaction.v1.server.client.model.NerEntities;
import com.iqser.red.service.redaction.v1.server.exception.RedactionException;
import com.iqser.red.service.redaction.v1.server.redaction.model.Dictionary;
import com.iqser.red.service.redaction.v1.server.redaction.model.DictionaryIncrement;
import com.iqser.red.service.redaction.v1.server.redaction.model.DictionaryIncrementValue;
import com.iqser.red.service.redaction.v1.server.redaction.model.DictionaryVersion;
import com.iqser.red.service.redaction.v1.server.redaction.model.Image;
import com.iqser.red.service.redaction.v1.server.redaction.model.PageEntities;
import com.iqser.red.service.redaction.v1.server.redaction.model.PdfImage;
import com.iqser.red.service.redaction.v1.server.redaction.model.RedRectangle2D;
import com.iqser.red.service.redaction.v1.server.redaction.utils.EntitySearchUtils;
import com.iqser.red.service.redaction.v1.server.redaction.utils.SearchImplementation;
import com.iqser.red.service.redaction.v1.server.segmentation.ImageService;
import com.iqser.red.service.redaction.v1.server.segmentation.PdfSegmentationService;
import com.iqser.red.service.redaction.v1.server.settings.RedactionServiceSettings;
import com.iqser.red.service.redaction.v1.server.storage.RedactionStorageService;
import io.micrometer.core.annotation.Timed;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@@ -56,10 +77,12 @@ public class AnalyzeService {
private final RedactionServiceSettings redactionServiceSettings;
private final SectionTextBuilderService sectionTextBuilderService;
private final SectionGridCreatorService sectionGridCreatorService;
private final NerAnalyserService nerAnalyserService;
private final ImageService imageService;
private final ImportedRedactionService importedRedactionService;
public void analyzeDocumentStructure(StructureAnalyzeRequest analyzeRequest) {
@Timed("redactmanager_analyzeDocumentStructure")
public AnalyzeResult analyzeDocumentStructure(StructureAnalyzeRequest analyzeRequest) {
long startTime = System.currentTimeMillis();
@@ -67,60 +90,48 @@ public class AnalyzeService {
Document classifiedDoc;
try {
var storedObjectStream = redactionStorageService.getStoredObject(RedactionStorageService.StorageIdUtils.getStorageId(analyzeRequest
.getDossierId(), analyzeRequest.getFileId(), FileType.ORIGIN));
classifiedDoc = pdfSegmentationService.parseDocument(storedObjectStream);
var storedObjectStream = redactionStorageService.getStoredObject(RedactionStorageService.StorageIdUtils.getStorageId(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), FileType.ORIGIN));
Map<Integer, List<PdfImage>> pdfImages = null;
if (redactionServiceSettings.isEnableImageClassification()) {
pdfImages = imageService.convertImages(analyzeRequest.getDossierId(), analyzeRequest.getFileId());
}
classifiedDoc = pdfSegmentationService.parseDocument(storedObjectStream, pdfImages);
pageCount = classifiedDoc.getPages().size();
} catch (Exception e) {
throw new RedactionException(e);
}
List<SectionText> sectionTexts = sectionTextBuilderService.buildSectionText(classifiedDoc);
sectionGridCreatorService.createSectionGrid(classifiedDoc, pageCount);
Text text = new Text(pageCount, sectionTexts);
// enhance section grid with headline data
sectionTexts.forEach(sectionText -> classifiedDoc.getSectionGrid()
.getSections()
.add(new SectionGrid.SectionGridSection(sectionText.getSectionNumber(), sectionText.getHeadline(), sectionText.getSectionAreas()
.stream()
.map(SectionArea::getPage)
.collect(Collectors.toSet()), sectionText.getSectionAreas())));
log.info("Store text, simplified text and section grid for file {} in dossier {}", analyzeRequest.getFileId(), analyzeRequest.getDossierId());
redactionStorageService.storeObject(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), FileType.TEXT, text);
redactionStorageService.storeObject(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), FileType.SECTION_GRID, classifiedDoc
.getSectionGrid());
redactionStorageService.storeObject(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), FileType.SIMPLIFIED_TEXT, convert(text));
redactionStorageService.storeObject(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), FileType.SECTION_GRID, classifiedDoc.getSectionGrid());
log.info("Document structure analysis successful, took: {}", System.currentTimeMillis() - startTime);
}
public AnalyzeResult analyze(AnalyzeRequest analyzeRequest) {
long startTime = System.currentTimeMillis();
var text = redactionStorageService.getText(analyzeRequest.getDossierId(), analyzeRequest.getFileId());
var nerEntities = redactionStorageService.getNerEntities(analyzeRequest.getDossierId(), analyzeRequest.getFileId());
if(redactionServiceSettings.isEnableEntityRecognition() && nerEntities == null){
nerAnalyserService.computeNerEntities(analyzeRequest.getDossierId(), analyzeRequest.getFileId());
nerEntities = redactionStorageService.getNerEntities(analyzeRequest.getDossierId(), analyzeRequest.getFileId());
}
dictionaryService.updateDictionary(analyzeRequest.getDossierTemplateId(), analyzeRequest.getDossierId());
KieContainer kieContainer = droolsExecutionService.updateRules(analyzeRequest.getDossierTemplateId());
long rulesVersion = droolsExecutionService.getRulesVersion(analyzeRequest.getDossierTemplateId());
Dictionary dictionary = dictionaryService.getDeepCopyDictionary(analyzeRequest.getDossierTemplateId(), analyzeRequest
.getDossierId());
PageEntities pageEntities = entityRedactionService.findEntities(dictionary, text.getSectionTexts(), kieContainer, analyzeRequest, nerEntities);
dictionaryService.updateExternalDictionary(dictionary, analyzeRequest.getDossierTemplateId(), analyzeRequest.getDossierId());
List<RedactionLogEntry> redactionLogEntries = redactionLogCreatorService.createRedactionLog(pageEntities, text.getNumberOfPages(), analyzeRequest
.getDossierTemplateId());
var legalBasis = legalBasisClient.getLegalBasisMapping(analyzeRequest.getDossierTemplateId());
var redactionLog = new RedactionLog(redactionServiceSettings.getAnalysisVersion(), redactionLogEntries, legalBasis, dictionary
.getVersion()
.getDossierTemplateVersion(), dictionary.getVersion()
.getDossierVersion(), rulesVersion, legalBasisClient.getVersion(analyzeRequest.getDossierTemplateId()));
return finalizeAnalysis(analyzeRequest, startTime, redactionLog, text, dictionary.getVersion(), false);
return AnalyzeResult.builder()
.dossierId(analyzeRequest.getDossierId())
.fileId(analyzeRequest.getFileId())
.duration(System.currentTimeMillis() - startTime)
.numberOfPages(text.getNumberOfPages())
.analysisVersion(redactionServiceSettings.getAnalysisVersion())
.build();
}
@Timed("redactmanager_reanalyze")
@SneakyThrows
public AnalyzeResult reanalyze(@RequestBody AnalyzeRequest analyzeRequest) {
@@ -134,8 +145,7 @@ public class AnalyzeService {
return analyze(analyzeRequest);
}
DictionaryIncrement dictionaryIncrement = dictionaryService.getDictionaryIncrements(analyzeRequest.getDossierTemplateId(), new DictionaryVersion(redactionLog
.getDictionaryVersion(), redactionLog.getDossierDictionaryVersion()), analyzeRequest.getDossierId());
DictionaryIncrement dictionaryIncrement = dictionaryService.getDictionaryIncrements(analyzeRequest.getDossierTemplateId(), new DictionaryVersion(redactionLog.getDictionaryVersion(), redactionLog.getDossierDictionaryVersion()), analyzeRequest.getDossierId());
Set<Integer> sectionsToReanalyse = !analyzeRequest.getSectionsToReanalyse()
.isEmpty() ? analyzeRequest.getSectionsToReanalyse() : findSectionsToReanalyse(dictionaryIncrement, redactionLog, text, analyzeRequest);
@@ -144,10 +154,11 @@ public class AnalyzeService {
return finalizeAnalysis(analyzeRequest, startTime, redactionLog, text, dictionaryIncrement.getDictionaryVersion(), true);
}
var nerEntities = redactionStorageService.getNerEntities(analyzeRequest.getDossierId(), analyzeRequest.getFileId());
if(redactionServiceSettings.isEnableEntityRecognition() && nerEntities == null){
nerAnalyserService.computeNerEntities(analyzeRequest.getDossierId(), analyzeRequest.getFileId());
NerEntities nerEntities;
if (redactionServiceSettings.isNerServiceEnabled()) {
nerEntities = redactionStorageService.getNerEntities(analyzeRequest.getDossierId(), analyzeRequest.getFileId());
} else {
nerEntities = new NerEntities();
}
List<SectionText> reanalysisSections = text.getSectionTexts()
@@ -157,19 +168,57 @@ public class AnalyzeService {
KieContainer kieContainer = droolsExecutionService.updateRules(analyzeRequest.getDossierTemplateId());
Dictionary dictionary = dictionaryService.getDeepCopyDictionary(analyzeRequest.getDossierTemplateId(), analyzeRequest
.getDossierId());
Dictionary dictionary = dictionaryService.getDeepCopyDictionary(analyzeRequest.getDossierTemplateId(), analyzeRequest.getDossierId());
PageEntities pageEntities = entityRedactionService.findEntities(dictionary, reanalysisSections, kieContainer, analyzeRequest, nerEntities);
var newRedactionLogEntries = redactionLogCreatorService.createRedactionLog(pageEntities, text.getNumberOfPages(), analyzeRequest
.getDossierTemplateId());
redactionLog.getRedactionLogEntry().removeIf(entry -> sectionsToReanalyse.contains(entry.getSectionNumber()));
redactionLog.getRedactionLogEntry().addAll(newRedactionLogEntries);
var newRedactionLogEntries = redactionLogCreatorService.createRedactionLog(pageEntities, text.getNumberOfPages(), analyzeRequest.getDossierTemplateId());
var importedRedactionFilteredEntries = importedRedactionService.processImportedRedactions(analyzeRequest.getDossierTemplateId(), analyzeRequest.getDossierId(), analyzeRequest.getFileId(), newRedactionLogEntries, false);
redactionLog.getRedactionLogEntry()
.removeIf(entry -> sectionsToReanalyse.contains(entry.getSectionNumber()) && !entry.getType()
.equals(IMPORTED_REDACTION_TYPE));
redactionLog.getRedactionLogEntry().addAll(importedRedactionFilteredEntries);
return finalizeAnalysis(analyzeRequest, startTime, redactionLog, text, dictionaryIncrement.getDictionaryVersion(), true);
}
@Timed("redactmanager_analyze")
public AnalyzeResult analyze(AnalyzeRequest analyzeRequest) {
long startTime = System.currentTimeMillis();
var text = redactionStorageService.getText(analyzeRequest.getDossierId(), analyzeRequest.getFileId());
NerEntities nerEntities;
if (redactionServiceSettings.isNerServiceEnabled()) {
nerEntities = redactionStorageService.getNerEntities(analyzeRequest.getDossierId(), analyzeRequest.getFileId());
} else {
nerEntities = new NerEntities();
}
dictionaryService.updateDictionary(analyzeRequest.getDossierTemplateId(), analyzeRequest.getDossierId());
KieContainer kieContainer = droolsExecutionService.updateRules(analyzeRequest.getDossierTemplateId());
long rulesVersion = droolsExecutionService.getRulesVersion(analyzeRequest.getDossierTemplateId());
Dictionary dictionary = dictionaryService.getDeepCopyDictionary(analyzeRequest.getDossierTemplateId(), analyzeRequest.getDossierId());
PageEntities pageEntities = entityRedactionService.findEntities(dictionary, text.getSectionTexts(), kieContainer, analyzeRequest, nerEntities);
List<RedactionLogEntry> redactionLogEntries = redactionLogCreatorService.createRedactionLog(pageEntities, text.getNumberOfPages(), analyzeRequest.getDossierTemplateId());
var legalBasis = legalBasisClient.getLegalBasisMapping(analyzeRequest.getDossierTemplateId());
var redactionLog = new RedactionLog(redactionServiceSettings.getAnalysisVersion(), analyzeRequest.getAnalysisNumber(), redactionLogEntries, convert(legalBasis), dictionary.getVersion()
.getDossierTemplateVersion(), dictionary.getVersion()
.getDossierVersion(), rulesVersion, legalBasisClient.getVersion(analyzeRequest.getDossierTemplateId()));
var importedRedactionFilteredEntries = importedRedactionService.processImportedRedactions(analyzeRequest.getDossierTemplateId(), analyzeRequest.getDossierId(), analyzeRequest.getFileId(), redactionLog.getRedactionLogEntry(), true);
redactionLog.setRedactionLogEntry(importedRedactionFilteredEntries);
return finalizeAnalysis(analyzeRequest, startTime, redactionLog, text, dictionary.getVersion(), false);
}
@Timed("redactmanager_findSectionsToReanalyse")
private Set<Integer> findSectionsToReanalyse(DictionaryIncrement dictionaryIncrement, RedactionLog redactionLog,
Text text, AnalyzeRequest analyzeRequest) {
@@ -179,7 +228,7 @@ public class AnalyzeService {
Set<Integer> sectionsToReanalyse = new HashSet<>();
Map<Integer, Set<Image>> imageEntries = new HashMap<>();
for (RedactionLogEntry entry : redactionLog.getRedactionLogEntry()) {
if (entry.isManual() || relevantManuallyModifiedAnnotationIds.contains(entry.getId())) {
if (entry.isLocalManualRedaction() || relevantManuallyModifiedAnnotationIds.contains(entry.getId())) {
sectionsToReanalyse.add(entry.getSectionNumber());
}
if (entry.isImage()) {
@@ -187,9 +236,13 @@ public class AnalyzeService {
}
}
var dictionaryIncrementsSearch = new SearchImplementation(dictionaryIncrement.getValues().stream()
.map(DictionaryIncrementValue::getValue).collect(Collectors.toList()), true);
for (SectionText sectionText : text.getSectionTexts()) {
if (EntitySearchUtils.sectionContainsAny(sectionText.getText(), dictionaryIncrement.getValues())) {
if (EntitySearchUtils.sectionContainsAny(sectionText.getText(), dictionaryIncrementsSearch)) {
sectionsToReanalyse.add(sectionText.getSectionNumber());
}
@@ -210,9 +263,8 @@ public class AnalyzeService {
excludeExcludedPages(redactionLog, analyzeRequest.getExcludedPages());
var redactionLogChange = redactionChangeLogService.computeChanges(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), redactionLog);
redactionStorageService.storeObject(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), FileType.REDACTION_LOG, redactionLogChange
.getRedactionLog());
var redactionLogChange = redactionChangeLogService.computeChanges(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), redactionLog, analyzeRequest.getAnalysisNumber());
redactionStorageService.storeObject(analyzeRequest.getDossierId(), analyzeRequest.getFileId(), FileType.REDACTION_LOG, redactionLogChange.getRedactionLog());
long duration = System.currentTimeMillis() - startTime;
@@ -223,11 +275,13 @@ public class AnalyzeService {
.numberOfPages(text.getNumberOfPages())
.hasUpdates(redactionLogChange.isHasChanges())
.analysisVersion(redactionServiceSettings.getAnalysisVersion())
.analysisNumber(analyzeRequest.getAnalysisNumber())
.rulesVersion(redactionLog.getRulesVersion())
.dictionaryVersion(redactionLog.getDictionaryVersion())
.legalBasisVersion(redactionLog.getLegalBasisVersion())
.dossierDictionaryVersion(redactionLog.getDossierDictionaryVersion())
.wasReanalyzed(isReanalysis)
.manualRedactions(analyzeRequest.getManualRedactions())
.build();
}
@@ -238,16 +292,22 @@ public class AnalyzeService {
return new HashSet<>();
}
return Stream.concat(manualRedactions.getLegalBasisChanges()
return Stream.concat(manualRedactions.getResizeRedactions()
.stream()
.map(ManualResizeRedaction::getAnnotationId), Stream.concat(manualRedactions.getLegalBasisChanges()
.stream()
.map(ManualLegalBasisChange::getAnnotationId), Stream.concat(manualRedactions.getImageRecategorization()
.stream()
.map(ManualImageRecategorization::getAnnotationId), Stream.concat(manualRedactions.getIdsToRemove()
.stream()
.map(IdRemoval::getAnnotationId), manualRedactions.getForceRedactions().stream().map(ManualForceRedaction::getAnnotationId))))
.collect(Collectors.toSet());
.map(IdRemoval::getAnnotationId), manualRedactions.getForceRedactions()
.stream()
.map(ManualForceRedaction::getAnnotationId))))).collect(Collectors.toSet());
}
public List<RedactionLogLegalBasis> convert(List<LegalBasis> legalBasis) {
return legalBasis.stream().map(l -> new RedactionLogLegalBasis(l.getName(), l.getDescription(), l.getReason())).collect(Collectors.toList());
}
public Image convert(RedactionLogEntry entry) {
@@ -276,4 +336,17 @@ public class AnalyzeService {
}
}
private SimplifiedText convert(Text text) {
List<SimplifiedSectionText> sectionTexts = new ArrayList<>();
int numberOfPages = 0;
if (text != null) {
numberOfPages = text.getNumberOfPages();
text.getSectionTexts().stream().filter(Objects::nonNull).forEach(st -> sectionTexts.add(new SimplifiedSectionText(st.getSectionNumber(), st.getText())));
}
return SimplifiedText.builder().numberOfPages(numberOfPages).sectionTexts(sectionTexts).build();
}
}
@@ -5,51 +5,57 @@ import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.ty
import com.iqser.red.service.redaction.v1.server.client.DictionaryClient;
import com.iqser.red.service.redaction.v1.server.redaction.model.Dictionary;
import com.iqser.red.service.redaction.v1.server.redaction.model.*;
import com.iqser.red.service.redaction.v1.server.redaction.utils.IdBuilder;
import feign.FeignException;
import io.micrometer.core.annotation.Timed;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.SerializationUtils;
import org.springframework.stereotype.Service;
import java.awt.Color;
import java.awt.*;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class DictionaryService {
private static final String DEFAULT_COLOR = "#cccccc";
private final DictionaryClient dictionaryClient;
private final Map<String, DictionaryRepresentation> dictionariesByDossierTemplate = new HashMap<>();
private final Map<String, DictionaryRepresentation> dictionariesByDossier = new HashMap<>();
@Timed("redactmanager_updateDictionary")
public DictionaryVersion updateDictionary(String dossierTemplateId, String dossierId) {
log.info("Updating dictionary data for dossierTemplate {} and dossier {}", dossierTemplateId, dossierId);
long dossierTemplateDictionaryVersion = dictionaryClient.getVersion(dossierTemplateId);
var dossierTemplateDictionary = dictionariesByDossierTemplate.get(dossierTemplateId);
if (dossierTemplateDictionary == null || dossierTemplateDictionaryVersion > dossierTemplateDictionary.getDictionaryVersion()) {
updateDictionaryEntry(dossierTemplateId, dossierTemplateDictionaryVersion, null);
updateDictionaryEntry(dossierTemplateId, dossierTemplateDictionaryVersion, getVersion(dossierTemplateDictionary), null);
}
long dossierDictionaryVersion = dictionaryClient.getVersionForDossier(dossierId);
var dossierDictionary = dictionariesByDossier.get(dossierId);
if (dossierDictionary == null || dossierDictionaryVersion > dossierDictionary.getDictionaryVersion()) {
updateDictionaryEntry(dossierTemplateId, dossierDictionaryVersion, dossierId);
updateDictionaryEntry(dossierTemplateId, dossierDictionaryVersion, getVersion(dossierDictionary), dossierId);
}
return DictionaryVersion.builder().dossierTemplateVersion(dossierTemplateDictionaryVersion).dossierVersion(dossierDictionaryVersion).build();
return DictionaryVersion.builder()
.dossierTemplateVersion(dossierTemplateDictionaryVersion)
.dossierVersion(dossierDictionaryVersion)
.build();
}
public DictionaryIncrement getDictionaryIncrements(String dossierTemplateId, DictionaryVersion fromVersion, String dossierId) {
@Timed("redactmanager_getDictionaryIncrements")
public DictionaryIncrement getDictionaryIncrements(String dossierTemplateId, DictionaryVersion fromVersion,
String dossierId) {
DictionaryVersion version = updateDictionary(dossierTemplateId, dossierId);
@@ -61,9 +67,19 @@ public class DictionaryService {
newValues.add(new DictionaryIncrementValue(dictionaryEntry.getValue(), dictionaryModel.isCaseInsensitive()));
}
});
dictionaryModel.getFalsePositives().forEach(dictionaryEntry -> {
if (dictionaryEntry.getVersion() > fromVersion.getDossierTemplateVersion()) {
newValues.add(new DictionaryIncrementValue(dictionaryEntry.getValue(), dictionaryModel.isCaseInsensitive()));
}
});
dictionaryModel.getFalseRecommendations().forEach(dictionaryEntry -> {
if (dictionaryEntry.getVersion() > fromVersion.getDossierTemplateVersion()) {
newValues.add(new DictionaryIncrementValue(dictionaryEntry.getValue(), dictionaryModel.isCaseInsensitive()));
}
});
});
if(dictionariesByDossier.containsKey(dossierId)) {
if (dictionariesByDossier.containsKey(dossierId)) {
dictionaryModels = dictionariesByDossier.get(dossierId).getDictionary();
dictionaryModels.forEach(dictionaryModel -> {
dictionaryModel.getEntries().forEach(dictionaryEntry -> {
@@ -71,6 +87,16 @@ public class DictionaryService {
newValues.add(new DictionaryIncrementValue(dictionaryEntry.getValue(), dictionaryModel.isCaseInsensitive()));
}
});
dictionaryModel.getFalsePositives().forEach(dictionaryEntry -> {
if (dictionaryEntry.getVersion() > fromVersion.getDossierVersion()) {
newValues.add(new DictionaryIncrementValue(dictionaryEntry.getValue(), dictionaryModel.isCaseInsensitive()));
}
});
dictionaryModel.getFalseRecommendations().forEach(dictionaryEntry -> {
if (dictionaryEntry.getVersion() > fromVersion.getDossierVersion()) {
newValues.add(new DictionaryIncrementValue(dictionaryEntry.getValue(), dictionaryModel.isCaseInsensitive()));
}
});
});
}
@@ -78,34 +104,88 @@ public class DictionaryService {
}
private void updateDictionaryEntry(String dossierTemplateId, long version, String dossierId) {
private void updateDictionaryEntry(String dossierTemplateId, long version, Long currentVersion, String dossierId) {
try {
DictionaryRepresentation dictionaryRepresentation = new DictionaryRepresentation();
var typeResponse = dossierId == null ? dictionaryClient.getAllTypesForDossierTemplate(dossierTemplateId) : dictionaryClient.getAllTypesForDossier(dossierId);
var typeResponse = dossierId == null ? dictionaryClient.getAllTypesForDossierTemplate(dossierTemplateId, false) : dictionaryClient.getAllTypesForDossier(dossierId, false);
if (CollectionUtils.isNotEmpty(typeResponse)) {
List<DictionaryModel> dictionary = typeResponse
.stream()
.map(t -> new DictionaryModel(t.getType(), t.getRank(), convertColor(t.getHexColor()), t.isCaseInsensitive(), t
.isHint(), t.isRecommendation(), convertEntries(t.getId()), new HashSet<>(), dossierId != null))
.sorted(Comparator.comparingInt(DictionaryModel::getRank).reversed())
.collect(Collectors.toList());
List<DictionaryModel> dictionary = typeResponse.stream().map(t -> {
Optional<DictionaryModel> oldModel;
if (dossierId == null) {
var representation = dictionariesByDossierTemplate.get(dossierTemplateId);
oldModel = representation != null ? representation.getDictionary()
.stream()
.filter(f -> f.getType().equals(t.getType()))
.findAny() : Optional.empty();
} else {
var representation = dictionariesByDossier.get(dossierId);
oldModel = representation != null ? representation.getDictionary()
.stream()
.filter(f -> f.getType().equals(t.getType()))
.findAny() : Optional.empty();
}
Set<DictionaryEntry> entries = new HashSet<>();
Set<DictionaryEntry> falsePositives = new HashSet<>();
Set<DictionaryEntry> falseRecommendations = new HashSet<>();
DictionaryEntries newEntries = getEntries(t.getId(), currentVersion);
var newValues = newEntries.getEntries()
.stream()
.map(DictionaryEntry::getValue)
.collect(Collectors.toSet());
var newFalsePositivesValues = newEntries.getFalsePositives()
.stream()
.map(DictionaryEntry::getValue)
.collect(Collectors.toSet());
var newFalseRecommendationsValues = newEntries.getFalseRecommendations()
.stream()
.map(DictionaryEntry::getValue)
.collect(Collectors.toSet());
oldModel.ifPresent(oldDictionaryModel -> {
});
// add old entries from existing DictionaryModel
oldModel.ifPresent(dictionaryModel -> entries.addAll(dictionaryModel.getEntries()
.stream()
.filter(f -> !newValues.contains(f.getValue()))
.collect(Collectors.toList())));
oldModel.ifPresent(dictionaryModel -> falsePositives.addAll(dictionaryModel.getFalsePositives()
.stream()
.filter(f -> !newFalsePositivesValues.contains(f.getValue()))
.collect(Collectors.toList())));
oldModel.ifPresent(dictionaryModel -> falseRecommendations.addAll(dictionaryModel.getFalseRecommendations()
.stream()
.filter(f -> !newFalseRecommendationsValues.contains(f.getValue()))
.collect(Collectors.toList())));
// Add Increments
entries.addAll(newEntries.getEntries());
falsePositives.addAll(newEntries.getFalsePositives());
falseRecommendations.addAll(newEntries.getFalseRecommendations());
return new DictionaryModel(t.getType(), t.getRank(), convertColor(t.getHexColor()), t.isCaseInsensitive(), t.isHint(), entries, falsePositives, falseRecommendations, dossierId != null);
}).sorted(Comparator.comparingInt(DictionaryModel::getRank).reversed()).collect(Collectors.toList());
dictionary.forEach(dm -> dictionaryRepresentation.getLocalAccessMap().put(dm.getType(), dm));
Colors colors = dictionaryClient.getColors(dossierTemplateId);
dictionaryRepresentation.setDefaultColor(convertColor(colors.getDefaultColor()));
dictionaryRepresentation.setRequestAddColor(convertColor(colors.getRequestAdd()));
dictionaryRepresentation.setRequestRemoveColor(convertColor(colors.getRequestRemove()));
dictionaryRepresentation.setNotRedactedColor(convertColor(colors.getNotRedacted()));
dictionaryRepresentation.setDefaultColor(convertColor(DEFAULT_COLOR));
dictionaryRepresentation.setRequestAddColor(convertColor(colors.getRequestAddColor()));
dictionaryRepresentation.setRequestRemoveColor(convertColor(colors.getRequestRemoveColor()));
dictionaryRepresentation.setNotRedactedColor(convertColor(colors.getSkippedColor()));
dictionaryRepresentation.setDossierTemplateId(dossierTemplateId);
dictionaryRepresentation.setDictionaryVersion(version);
dictionaryRepresentation.setDictionary(dictionary);
if(dossierId == null) {
if (dossierId == null) {
dictionariesByDossierTemplate.put(dossierTemplateId, dictionaryRepresentation);
} else {
dictionariesByDossier.put(dossierId, dictionaryRepresentation);
@@ -118,31 +198,21 @@ public class DictionaryService {
}
public void updateExternalDictionary(Dictionary dictionary, String dossierTemplateId, String dossierId) {
private DictionaryEntries getEntries(String typeId, Long fromVersion) {
dictionary.getDictionaryModels().forEach(dm -> {
if (dm.isRecommendation() && !dm.getLocalEntries().isEmpty()) {
dictionaryClient.addEntries(IdBuilder.getTypeId(dm.getType(), dossierTemplateId, dm.isDossierDictionary() ? dossierId : null), new ArrayList<>(dm.getLocalEntries()), false);
long externalVersion = dictionaryClient.getVersion(dossierTemplateId);
if (externalVersion == dictionary.getVersion().getDossierTemplateVersion() + 1) {
dictionary.getVersion().setDossierTemplateVersion(externalVersion);
}
}
});
}
var type = dictionaryClient.getDictionaryForType(typeId, fromVersion);
private Set<DictionaryEntry> convertEntries(String typeId) {
var type = dictionaryClient.getDictionaryForType(typeId);
Set<DictionaryEntry> entries = new HashSet<>(type
.getEntries());
Set<DictionaryEntry> entries = type.getEntries() != null ? new HashSet<>(type.getEntries()) : new HashSet<>();
Set<DictionaryEntry> falsePositives = type.getFalsePositiveEntries() != null ? new HashSet<>(type.getFalsePositiveEntries()) : new HashSet<>();
Set<DictionaryEntry> falseRecommendations = type.getFalseRecommendationEntries() != null ? new HashSet<>(type.getFalseRecommendationEntries()) : new HashSet<>();
if (type.isCaseInsensitive()) {
entries.forEach(entry -> entry.setValue(entry.getValue().toLowerCase(Locale.ROOT)));
falsePositives.forEach(entry -> entry.setValue(entry.getValue().toLowerCase(Locale.ROOT)));
falseRecommendations.forEach(entry -> entry.setValue(entry.getValue().toLowerCase(Locale.ROOT)));
}
return entries;
log.info("Dictionary update returned {} entries {} falsePositives and {} falseRecommendations for type {}", entries.size(), falsePositives.size(), falseRecommendations.size(), type.getType());
return new DictionaryEntries(entries, falsePositives, falseRecommendations);
}
@@ -153,16 +223,6 @@ public class DictionaryService {
}
public boolean isCaseInsensitiveDictionary(String type, String dossierTemplateId) {
DictionaryModel dictionaryModel = dictionariesByDossierTemplate.get(dossierTemplateId).getLocalAccessMap().get(type);
if (dictionaryModel != null) {
return dictionaryModel.isCaseInsensitive();
}
return false;
}
public float[] getColor(String type, String dossierTemplateId) {
DictionaryModel model = dictionariesByDossierTemplate.get(dossierTemplateId).getLocalAccessMap().get(type);
@@ -183,16 +243,7 @@ public class DictionaryService {
}
public boolean isRecommendation(String type, String dossierTemplateId) {
DictionaryModel model = dictionariesByDossierTemplate.get(dossierTemplateId).getLocalAccessMap().get(type);
if (model != null) {
return model.isRecommendation();
}
return false;
}
@Timed("redactmanager_getDeepCopyDictionary")
public Dictionary getDeepCopyDictionary(String dossierTemplateId, String dossierId) {
List<DictionaryModel> copy = new ArrayList<>();
@@ -204,7 +255,7 @@ public class DictionaryService {
//TODO merge dictionaries if they have same names
long dossierDictionaryVersion = -1;
if(dictionariesByDossier.containsKey(dossierId)) {
if (dictionariesByDossier.containsKey(dossierId)) {
var dossierRepresentation = dictionariesByDossier.get(dossierId);
dossierRepresentation.getDictionary().forEach(dm -> {
copy.add(SerializationUtils.clone(dm));
@@ -212,13 +263,12 @@ public class DictionaryService {
dossierDictionaryVersion = dossierRepresentation.getDictionaryVersion();
}
return new Dictionary(copy.stream().sorted(Comparator.comparingInt(DictionaryModel::getRank).reversed()).collect(Collectors.toList()), DictionaryVersion.builder().dossierTemplateVersion(dossierTemplateRepresentation.getDictionaryVersion()).dossierVersion(dossierDictionaryVersion).build());
}
public float[] getRequestRemoveColor(String dossierTemplateId) {
return dictionariesByDossierTemplate.get(dossierTemplateId).getRequestRemoveColor();
return new Dictionary(copy.stream()
.sorted(Comparator.comparingInt(DictionaryModel::getRank).reversed())
.collect(Collectors.toList()), DictionaryVersion.builder()
.dossierTemplateVersion(dossierTemplateRepresentation.getDictionaryVersion())
.dossierVersion(dossierDictionaryVersion)
.build());
}
@@ -228,9 +278,13 @@ public class DictionaryService {
}
public float[] getRequestAddColor(String dossierTemplateId) {
private Long getVersion(DictionaryRepresentation dictionaryRepresentation) {
return dictionariesByDossierTemplate.get(dossierTemplateId).getRequestAddColor();
if (dictionaryRepresentation == null) {
return null;
} else {
return dictionaryRepresentation.getDictionaryVersion();
}
}
}
@@ -3,6 +3,8 @@ package com.iqser.red.service.redaction.v1.server.redaction.service;
import com.iqser.red.service.redaction.v1.server.client.RulesClient;
import com.iqser.red.service.redaction.v1.server.exception.RulesValidationException;
import com.iqser.red.service.redaction.v1.server.redaction.model.Section;
import io.micrometer.core.annotation.Timed;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.kie.api.KieServices;
@@ -41,6 +43,7 @@ public class DroolsExecutionService {
}
@Timed("redactmanager_executeRules")
public Section executeRules(KieContainer kieContainer, Section section) {
KieSession kieSession = kieContainer.newKieSession();
@@ -9,30 +9,35 @@ import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.AnnotationStatus;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.ManualImageRecategorization;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.kie.api.runtime.KieContainer;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.AnnotationStatus;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.ManualRedactions;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.IdRemoval;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.ManualImageRecategorization;
import com.iqser.red.service.redaction.v1.model.AnalyzeRequest;
import com.iqser.red.service.redaction.v1.model.Engine;
import com.iqser.red.service.redaction.v1.server.classification.model.SectionText;
import com.iqser.red.service.redaction.v1.server.client.model.NerEntities;
import com.iqser.red.service.redaction.v1.server.redaction.model.Dictionary;
import com.iqser.red.service.redaction.v1.server.redaction.model.DictionaryModel;
import com.iqser.red.service.redaction.v1.server.redaction.model.Entities;
import com.iqser.red.service.redaction.v1.server.redaction.model.Entity;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityPositionSequence;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
import com.iqser.red.service.redaction.v1.server.redaction.model.Image;
import com.iqser.red.service.redaction.v1.server.redaction.model.PageEntities;
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
import com.iqser.red.service.redaction.v1.server.redaction.model.Section;
import com.iqser.red.service.redaction.v1.server.redaction.model.SectionSearchableTextPair;
import com.iqser.red.service.redaction.v1.server.redaction.utils.EntitySearchUtils;
import com.iqser.red.service.redaction.v1.server.redaction.utils.FindEntityDetails;
import com.iqser.red.service.redaction.v1.server.redaction.utils.IdBuilder;
import com.iqser.red.service.redaction.v1.server.settings.RedactionServiceSettings;
import io.micrometer.core.annotation.Timed;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -46,6 +51,7 @@ public class EntityRedactionService {
private final SurroundingWordsService surroundingWordsService;
public PageEntities findEntities(Dictionary dictionary, List<SectionText> sectionTexts, KieContainer kieContainer,
AnalyzeRequest analyzeRequest, NerEntities nerEntities) {
@@ -59,7 +65,9 @@ public class EntityRedactionService {
EntitySearchUtils.removeEntitiesContainedInLarger(entities);
}
Map<Integer, List<Entity>> entitiesPerPage = convertToEnititesPerPage(entities);
Map<Integer, List<Entity>> entitiesPerPage = convertToEntitiesPerPage(entities);
EntitySearchUtils.removeEntitiesContainedInRedactedLogos(imagesPerPage, entitiesPerPage);
return new PageEntities(entitiesPerPage, imagesPerPage);
}
@@ -72,38 +80,70 @@ public class EntityRedactionService {
List<SectionSearchableTextPair> sectionSearchableTextPairs = new ArrayList<>();
for (SectionText reanalysisSection : reanalysisSections) {
Set<Entity> entities = findEntities(reanalysisSection.getSearchableText(), reanalysisSection.getHeadline(), reanalysisSection
.getSectionNumber(), dictionary, local, nerEntities, reanalysisSection.getCellStarts());
Entities entities = findEntities(reanalysisSection.getSearchableText(), reanalysisSection.getHeadline(), reanalysisSection.getSectionNumber(), dictionary, local, nerEntities, reanalysisSection.getCellStarts(), analyzeRequest.getManualRedactions());
if (reanalysisSection.getCellStarts() != null && !reanalysisSection.getCellStarts().isEmpty()) {
surroundingWordsService.addSurroundingText(entities, reanalysisSection.getSearchableText(), dictionary, reanalysisSection
surroundingWordsService.addSurroundingText(entities.getEntities(), reanalysisSection.getSearchableText(), dictionary, reanalysisSection
.getCellStarts());
} else {
surroundingWordsService.addSurroundingText(entities, reanalysisSection.getSearchableText(), dictionary);
surroundingWordsService.addSurroundingText(entities.getEntities(), reanalysisSection.getSearchableText(), dictionary);
}
if (!local && reanalysisSection.getImages() != null && !reanalysisSection.getImages()
.isEmpty() && analyzeRequest.getManualRedactions() != null && analyzeRequest.getManualRedactions()
.getImageRecategorization() != null) {
for (Image image : reanalysisSection.getImages()) {
String imageId = IdBuilder.buildId(image.getPosition(), image.getPage());
for (ManualImageRecategorization imageRecategorization : analyzeRequest.getManualRedactions()
.getImageRecategorization()) {
if (imageRecategorization.getStatus().equals(AnnotationStatus.APPROVED) && imageRecategorization.getAnnotationId()
.equals(imageId)) {
image.setType(imageRecategorization.getType());
if (!local && analyzeRequest.getManualRedactions() != null) {
var approvedForceRedactions = analyzeRequest.getManualRedactions()
.getForceRedactions()
.stream()
.filter(fr -> fr.getStatus() == AnnotationStatus.APPROVED)
.filter(fr -> fr.getRequestDate() != null)
.collect(Collectors.toList());
// only approved id removals, that haven't been forced back afterwards
var idsToRemove = analyzeRequest.getManualRedactions()
.getIdsToRemove()
.stream()
.filter(idr -> idr.getStatus() == AnnotationStatus.APPROVED && !idr.isRemoveFromDictionary())
.filter(idr -> idr.getRequestDate() != null)
.filter(idr -> approvedForceRedactions.stream()
.noneMatch(forceRedact -> forceRedact.getAnnotationId()
.equals(idr.getAnnotationId()) && forceRedact.getRequestDate()
.isAfter(idr.getRequestDate())))
.map(IdRemoval::getAnnotationId)
.collect(Collectors.toSet());
if (reanalysisSection.getImages() != null && !reanalysisSection.getImages()
.isEmpty() && analyzeRequest.getManualRedactions().getImageRecategorization() != null) {
for (Image image : reanalysisSection.getImages()) {
String imageId = IdBuilder.buildId(image.getPosition(), image.getPage());
for (ManualImageRecategorization imageRecategorization : analyzeRequest.getManualRedactions()
.getImageRecategorization()) {
if (imageRecategorization.getStatus()
.equals(AnnotationStatus.APPROVED) && imageRecategorization.getAnnotationId()
.equals(imageId)) {
image.setType(imageRecategorization.getType());
}
}
if (idsToRemove.contains(imageId)) {
image.setIgnored(true);
}
}
}
entities.getEntities().forEach(entity -> entity.getPositionSequences().forEach(ps -> {
if (idsToRemove.contains(ps.getId())) {
entity.setIgnored(true);
}
}));
}
log.debug("Section {}, Images: {}", reanalysisSection.getSectionNumber(), reanalysisSection.getImages());
sectionSearchableTextPairs.add(new SectionSearchableTextPair(Section.builder()
.isLocal(false)
.dictionaryTypes(dictionary.getTypes())
.entities(hintsPerSectionNumber != null && hintsPerSectionNumber.containsKey(reanalysisSection.getSectionNumber()) ? Stream
.concat(entities.stream(), hintsPerSectionNumber.get(reanalysisSection.getSectionNumber())
.stream())
.collect(Collectors.toSet()) : entities)
.entities(hintsPerSectionNumber != null && hintsPerSectionNumber.containsKey(reanalysisSection.getSectionNumber()) ? Stream.concat(entities.getEntities()
.stream(), hintsPerSectionNumber.get(reanalysisSection.getSectionNumber()).stream())
.collect(Collectors.toSet()) : entities.getEntities())
.nerEntities(entities.getNerEntities())
.text(reanalysisSection.getSearchableText().getAsStringWithLinebreaks())
.searchText(reanalysisSection.getSearchableText().toString())
.headline(reanalysisSection.getHeadline())
@@ -112,14 +152,27 @@ public class EntityRedactionService {
.searchableText(reanalysisSection.getSearchableText())
.dictionary(dictionary)
.images(reanalysisSection.getImages())
.sectionAreas(reanalysisSection.getSectionAreas())
.fileAttributes(analyzeRequest.getFileAttributes())
.build(), reanalysisSection.getSearchableText()));
.manualRedactions(analyzeRequest.getManualRedactions())
.build(), reanalysisSection.getSearchableText(), reanalysisSection.getCellStarts()));
}
Set<Entity> entities = new HashSet<>();
sectionSearchableTextPairs.forEach(sectionSearchableTextPair -> {
Section analysedSection = droolsExecutionService.executeRules(kieContainer, sectionSearchableTextPair.getSection());
EntitySearchUtils.removeEntitiesContainedInLarger(analysedSection.getEntities());
var entriesWithoutSurroundingText = analysedSection.getEntities().stream().filter(e -> e.getTextAfter() == null && e.getTextBefore() == null).collect(Collectors.toSet());
if (sectionSearchableTextPair.getCellStarts() != null && !sectionSearchableTextPair.getCellStarts()
.isEmpty()) {
surroundingWordsService.addSurroundingText(entriesWithoutSurroundingText, sectionSearchableTextPair.getSearchableText(), dictionary, sectionSearchableTextPair.getCellStarts());
} else {
surroundingWordsService.addSurroundingText(entriesWithoutSurroundingText, sectionSearchableTextPair.getSearchableText(), dictionary);
}
entities.addAll(analysedSection.getEntities());
if (!local) {
@@ -128,13 +181,14 @@ public class EntityRedactionService {
}
addLocalValuesToDictionary(analysedSection, dictionary);
}
});
return entities;
}
private Map<Integer, List<Entity>> convertToEnititesPerPage(Set<Entity> entities) {
private Map<Integer, List<Entity>> convertToEntitiesPerPage(Set<Entity> entities) {
Map<Integer, List<Entity>> entitiesPerPage = new HashMap<>();
for (Entity entity : entities) {
@@ -146,10 +200,7 @@ public class EntityRedactionService {
for (Map.Entry<Integer, List<EntityPositionSequence>> entry : sequenceOnPage.entrySet()) {
entitiesPerPage.computeIfAbsent(entry.getKey(), (x) -> new ArrayList<>())
.add(new Entity(entity.getWord(), entity.getType(), entity.isRedaction(), entity.getRedactionReason(), entry
.getValue(), entity.getHeadline(), entity.getMatchedRule(), entity.getSectionNumber(), entity
.getLegalBasis(), entity.isDictionaryEntry(), entity.getTextBefore(), entity.getTextAfter(), entity
.getStart(), entity.getEnd(), entity.isDossierDictionaryEntry(), entity.getEngines(), entity.getReferences()));
.add(new Entity(entity.getWord(), entity.getType(), entity.isRedaction(), entity.getRedactionReason(), entry.getValue(), entity.getHeadline(), entity.getMatchedRule(), entity.getSectionNumber(), entity.getLegalBasis(), entity.isDictionaryEntry(), entity.getTextBefore(), entity.getTextAfter(), entity.getStart(), entity.getEnd(), entity.isDossierDictionaryEntry(), entity.getEngines(), entity.getReferences(), entity.getEntityType()));
}
}
return entitiesPerPage;
@@ -170,90 +221,75 @@ public class EntityRedactionService {
private void addLocalValuesToDictionary(Section analysedSection, Dictionary dictionary) {
analysedSection.getLocalDictionaryAdds().keySet().forEach(key -> {
if (dictionary.isRecommendation(key)) {
analysedSection.getLocalDictionaryAdds().get(key).forEach(value -> {
if (!dictionary.containsValue(key, value)) {
dictionary.getLocalAccessMap().get(key).getLocalEntries().add(value);
}
});
} else {
analysedSection.getLocalDictionaryAdds().get(key).forEach(value -> {
analysedSection.getLocalDictionaryAdds().keySet().forEach(key -> analysedSection.getLocalDictionaryAdds().get(key).forEach(value -> {
if (dictionary.getLocalAccessMap().get(key) == null) {
log.warn("Dictionary {} is null", key);
}
if (dictionary.getLocalAccessMap().get(key).getLocalEntries() == null) {
log.warn("Dictionary {} localEntries is null", key);
}
dictionary.getLocalAccessMap().get(key).getLocalEntries().add(value);
});
if (dictionary.getLocalAccessMap().get(key) == null) {
log.warn("Dictionary {} is null", key);
}
});
if (dictionary.getLocalAccessMap().get(key).getLocalEntries() == null) {
log.warn("Dictionary {} localEntries is null", key);
}
dictionary.getLocalAccessMap().get(key).getLocalEntries().add(value);
}));
}
private Set<Entity> findEntities(SearchableText searchableText, String headline, int sectionNumber,
Dictionary dictionary, boolean local, NerEntities nerEntities,
List<Integer> cellstarts) {
@Timed("redactmanager_findEntities")
private Entities findEntities(SearchableText searchableText, String headline, int sectionNumber,
Dictionary dictionary, boolean local, NerEntities nerEntities,
List<Integer> cellStarts, ManualRedactions manualRedactions) {
Set<Entity> found = new HashSet<>();
String searchableString = searchableText.toString();
String searchableString = searchableText.asString();
if (StringUtils.isEmpty(searchableString)) {
return found;
return new Entities(new HashSet<>(), new HashSet<>());
}
String lowercaseInputString = searchableString.toLowerCase();
for (DictionaryModel model : dictionary.getDictionaryModels()) {
if (model.isCaseInsensitive()) {
EntitySearchUtils.addOrAddEngine(found, EntitySearchUtils.find(lowercaseInputString, model.getValues(local), model
.getType(), headline, sectionNumber, !local, model.isDossierDictionary(), Engine.DICTIONARY));
} else {
EntitySearchUtils.addOrAddEngine(found, EntitySearchUtils.find(searchableString, model.getValues(local), model
.getType(), headline, sectionNumber, !local, model.isDossierDictionary(), Engine.DICTIONARY));
}
var searchImplementation = local ? model.getLocalSearch() : model.getEntriesSearch();
var entities = EntitySearchUtils.findEntities(model.isCaseInsensitive() ? lowercaseInputString : searchableString, searchImplementation, model, new FindEntityDetails(model.getType(), headline, sectionNumber, !local, model.isDossierDictionary(), local ? Engine.RULE : Engine.DICTIONARY, local ? EntityType.RECOMMENDATION : EntityType.ENTITY));
EntitySearchUtils.addOrAddEngine(found, entities);
}
Set<Entity> nerFound = new HashSet<>();
if (!local) {
Map<String, Set<String>> nerValuesPerType = getNerValues(sectionNumber, nerEntities, cellstarts);
nerValuesPerType.entrySet().forEach(entry -> {
EntitySearchUtils.addOrAddEngine(found, EntitySearchUtils.find(searchableString, entry.getValue(), entry
.getKey(), headline, sectionNumber, false, false, Engine.NER));
});
nerFound.addAll(getNerValues(sectionNumber, nerEntities, cellStarts, headline));
}
return EntitySearchUtils.clearAndFindPositions(found, searchableText, dictionary);
return new Entities(EntitySearchUtils.clearAndFindPositions(found, searchableText, dictionary, manualRedactions), nerFound);
}
private Map<String, Set<String>> getNerValues(int sectionNumber, NerEntities nerEntities,
List<Integer> cellstarts) {
private Set<Entity> getNerValues(int sectionNumber, NerEntities nerEntities, List<Integer> cellStarts,
String headline) {
Map<String, Set<String>> nerValuesPerType = new HashMap<>();
Set<Entity> entities = new HashSet<>();
if (redactionServiceSettings.isEnableEntityRecognition() && nerEntities.getResult()
.containsKey(sectionNumber)) {
nerEntities.getResult().get(sectionNumber).forEach(res -> {
if (cellstarts == null || cellstarts.isEmpty()) {
nerValuesPerType.computeIfAbsent(res.getType(), (a) -> new HashSet<>())
.add(new String(Base64.decodeBase64(res.getValue().getBytes())));
if (redactionServiceSettings.isNerServiceEnabled() && nerEntities.getData().containsKey(sectionNumber)) {
nerEntities.getData().get(sectionNumber).forEach(res -> {
if (cellStarts == null || cellStarts.isEmpty()) {
entities.add(new Entity(res.getValue(), res.getType(), res.getStartOffset(), res.getEndOffset(), headline, sectionNumber, false, false, Engine.NER, EntityType.RECOMMENDATION));
} else {
boolean intersectsCellStart = false;
for (Integer cellStart : cellstarts) {
for (Integer cellStart : cellStarts) {
if (res.getStartOffset() < cellStart && cellStart < res.getEndOffset()) {
intersectsCellStart = true;
break;
}
}
if (!intersectsCellStart) {
nerValuesPerType.computeIfAbsent(res.getType(), (a) -> new HashSet<>())
.add(new String(Base64.decodeBase64(res.getValue().getBytes())));
entities.add(new Entity(res.getValue(), res.getType(), res.getStartOffset(), res.getEndOffset(), headline, sectionNumber, false, false, Engine.NER, EntityType.RECOMMENDATION));
}
}
});
}
return nerValuesPerType;
return entities;
}
}
@@ -1,71 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.service;
import com.iqser.red.service.redaction.v1.server.classification.model.Page;
import com.iqser.red.service.redaction.v1.server.client.ImageClassificationClient;
import com.iqser.red.service.redaction.v1.server.client.ImageClassificationResponse;
import com.iqser.red.service.redaction.v1.server.client.MockMultipartFile;
import com.iqser.red.service.redaction.v1.server.redaction.model.ImageType;
import com.iqser.red.service.redaction.v1.server.redaction.model.PdfImage;
import com.iqser.red.service.redaction.v1.server.settings.RedactionServiceSettings;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.imageio.ImageIO;
import java.io.ByteArrayOutputStream;
@Slf4j
@Service
@RequiredArgsConstructor
public class ImageClassificationService {
private final ImageClassificationClient imageClassificationClient;
private final RedactionServiceSettings settings;
public void classifyImages(Page page) {
page.getImages().forEach(image -> {
if (settings.isEnableImageClassification() && !isEntirePageImage(image, page)) {
long start = System.currentTimeMillis();
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
ImageIO.write(image.getImage(), "png", baos);
var mockFile = new MockMultipartFile("file", "Image.png", "image/png", baos.toByteArray());
ImageClassificationResponse response = imageClassificationClient.classify(mockFile);
image.setImageType(ImageType.valueOf(response.getCategory()));
} catch (Exception e) {
log.error("Could not classify image", e);
image.setImageType(ImageType.OTHER);
}
log.info("Image classification took: " + (System.currentTimeMillis() - start));
} else {
image.setImageType(ImageType.OTHER);
}
image.getImage().flush();
image.setImage(null);
if (image.getImageType().equals(ImageType.OTHER)) {
page.getTextBlocks().forEach(textblock -> {
if (image.getPosition()
.contains(textblock.getMinX(), textblock.getMinY(), textblock.getWidth(), textblock.getHeight())) {
image.setImageType(ImageType.OCR);
}
});
}
});
}
private boolean isEntirePageImage(PdfImage image, Page page){
double imageArea = image.getPosition().getHeight() * image.getPosition().getWidth();
if(imageArea / page.getCropBoxArea() >= settings.getMaxImageCropboxRatio()){
log.info("Skipping image classification because images is almost as large as the entire page");
return true;
}
return false;
}
}
@@ -0,0 +1,151 @@
package com.iqser.red.service.redaction.v1.server.redaction.service;
import java.util.HashSet;
import java.util.List;
import org.springframework.stereotype.Service;
import com.iqser.red.service.redaction.v1.model.ImportedRedaction;
import com.iqser.red.service.redaction.v1.model.ImportedRedactions;
import com.iqser.red.service.redaction.v1.model.Point;
import com.iqser.red.service.redaction.v1.model.Rectangle;
import com.iqser.red.service.redaction.v1.model.RedactionLogEntry;
import com.iqser.red.service.redaction.v1.server.storage.RedactionStorageService;
import io.micrometer.core.annotation.Timed;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class ImportedRedactionService {
public static final String IMPORTED_REDACTION_TYPE = "imported_redaction";
private final DictionaryService dictionaryService;
private final RedactionStorageService redactionStorageService;
@Timed("redactmanager_processImportedRedactions")
public List<RedactionLogEntry> processImportedRedactions(String dossierTemplateId, String dossierId, String fileId,
List<RedactionLogEntry> redactionLogEntries,
boolean addImportedRedactions) {
var importedRedactions = redactionStorageService.getImportedRedactions(dossierId, fileId);
if (importedRedactions == null) {
return redactionLogEntries;
}
redactionLogEntries.forEach(redactionLogEntry -> addIntersections(redactionLogEntry, importedRedactions));
if (addImportedRedactions) {
return addImportedRedactionsRedactionLogEntries(dossierTemplateId, redactionLogEntries, importedRedactions);
}
return redactionLogEntries;
}
private List<RedactionLogEntry> addImportedRedactionsRedactionLogEntries(String dossierTemplateId,
List<RedactionLogEntry> redactionLogEntries,
ImportedRedactions importedRedactions) {
for (List<ImportedRedaction> importedRedactionsValues : importedRedactions.getImportedRedactions().values()) {
for (ImportedRedaction importedRedaction : importedRedactionsValues) {
RedactionLogEntry redactionLogEntry = RedactionLogEntry.builder()
.id(importedRedaction.getId())
.type(IMPORTED_REDACTION_TYPE)
.imported(true)
.redacted(true)
.positions(importedRedaction.getPositions())
.color(getColor(IMPORTED_REDACTION_TYPE, dossierTemplateId))
.build();
redactionLogEntries.add(redactionLogEntry);
}
}
return redactionLogEntries;
}
private void addIntersections(RedactionLogEntry redactionLogEntry, ImportedRedactions importedRedactions) {
for (Rectangle rectangle : redactionLogEntry.getPositions()) {
var normalizedRectangle = normalize(rectangle);
if (importedRedactions.getImportedRedactions().containsKey(rectangle.getPage())) {
var importedRedactionsOnPage = importedRedactions.getImportedRedactions().get(rectangle.getPage());
for (ImportedRedaction importedRedaction : importedRedactionsOnPage) {
for (Rectangle importedRedactionPosition : importedRedaction.getPositions()) {
if (rectOverlap(normalizedRectangle, normalize(importedRedactionPosition))) {
if (redactionLogEntry.getImportedRedactionIntersections() == null) {
redactionLogEntry.setImportedRedactionIntersections(new HashSet<>());
}
redactionLogEntry.getImportedRedactionIntersections().add(importedRedaction.getId());
}
}
}
}
}
}
boolean valueInRange(float value, float min, float max) {
return round(value) >= round(min) && round(value) <= round(max);
}
boolean rectOverlap(Rectangle a, Rectangle b) {
boolean xOverlap = valueInRange(a.getTopLeft().getX(), b.getTopLeft().getX(), b.getTopLeft()
.getX() + b.getWidth()) || valueInRange(b.getTopLeft().getX(), a.getTopLeft().getX(), a.getTopLeft()
.getX() + a.getWidth());
boolean yOverlap = valueInRange(a.getTopLeft().getY(), b.getTopLeft().getY(), b.getTopLeft()
.getY() + b.getHeight()) || valueInRange(b.getTopLeft().getY(), a.getTopLeft().getY(), a.getTopLeft()
.getY() + a.getHeight());
return xOverlap && yOverlap;
}
private Rectangle normalize(Rectangle rectangle) {
Rectangle r = new Rectangle();
Point p = new Point();
if (rectangle.getWidth() < 0) {
p.setX(rectangle.getTopLeft().getX() + rectangle.getWidth());
r.setWidth(Math.abs(rectangle.getWidth()));
} else {
p.setX(rectangle.getTopLeft().getX());
r.setWidth(rectangle.getWidth());
}
if (rectangle.getHeight() < 0) {
p.setY(rectangle.getTopLeft().getY() + rectangle.getHeight());
r.setHeight(Math.abs(rectangle.getHeight()));
} else {
p.setY(rectangle.getTopLeft().getY());
r.setHeight(rectangle.getHeight());
}
r.setTopLeft(p);
r.setPage(rectangle.getPage());
return r;
}
private float round(float value) {
double d = Math.pow(10, 0);
return (float) (Math.round(value * d) / d);
}
private float[] getColor(String type, String dossierTemplateId) {
return dictionaryService.getColor(type, dossierTemplateId);
}
}
@@ -0,0 +1,141 @@
package com.iqser.red.service.redaction.v1.server.redaction.service;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.ManualRedactions;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.Rectangle;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.ManualRedactionEntry;
import com.iqser.red.service.redaction.v1.model.AnalyzeResult;
import com.iqser.red.service.redaction.v1.model.Engine;
import com.iqser.red.service.redaction.v1.model.SectionArea;
import com.iqser.red.service.redaction.v1.server.classification.model.SectionText;
import com.iqser.red.service.redaction.v1.server.classification.model.Text;
import com.iqser.red.service.redaction.v1.server.parsing.model.TextPositionSequence;
import com.iqser.red.service.redaction.v1.server.redaction.model.Entity;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityPositionSequence;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
import com.iqser.red.service.redaction.v1.server.redaction.utils.EntitySearchUtils;
import com.iqser.red.service.redaction.v1.server.redaction.utils.FindEntityDetails;
import com.iqser.red.service.redaction.v1.server.redaction.utils.SearchImplementation;
import com.iqser.red.service.redaction.v1.server.storage.RedactionStorageService;
import io.micrometer.core.annotation.Timed;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
public class ManualRedactionSurroundingTextService {
private final RedactionStorageService redactionStorageService;
private final SurroundingWordsService surroundingWordsService;
@Timed("redactmanager_SurroundingTextAnalysis")
public AnalyzeResult addSurroundingText(String dossierId, String fileId, ManualRedactions manualRedactions) {
long startTime = System.currentTimeMillis();
Text text = redactionStorageService.getText(dossierId, fileId);
List<ManualRedactionEntry> processedAddRedactions = new ArrayList<>();
for (SectionText sectionText : text.getSectionTexts()) {
if (manualRedactions.getEntriesToAdd().isEmpty()) {
break;
}
for (SectionArea sectionArea : sectionText.getSectionAreas()) {
var addItty = manualRedactions.getEntriesToAdd().iterator();
while (addItty.hasNext()) {
var manualAddRedaction = addItty.next();
if (sectionContainsEntry(sectionArea, manualAddRedaction.getPositions())) {
var surroundingText = findSurroundingText(sectionText, manualAddRedaction.getValue(), manualAddRedaction.getPositions());
manualAddRedaction.setTextBefore(surroundingText.getLeft());
manualAddRedaction.setTextAfter(surroundingText.getRight());
processedAddRedactions.add(manualAddRedaction);
addItty.remove();
}
}
}
}
manualRedactions.getEntriesToAdd().addAll(processedAddRedactions);
return AnalyzeResult.builder()
.dossierId(dossierId)
.fileId(fileId)
.manualRedactions(manualRedactions)
.duration(System.currentTimeMillis() - startTime)
.build();
}
private Pair<String, String> findSurroundingText(SectionText sectionText, String value,
List<Rectangle> toFindPositions) {
Set<Entity> entities = EntitySearchUtils.find(sectionText.getText(), new SearchImplementation(value, false), new FindEntityDetails("dummy", sectionText.getHeadline(), sectionText.getSectionNumber(), false, false, Engine.DICTIONARY, EntityType.ENTITY));
Set<Entity> entitiesWithPositions = EntitySearchUtils.clearAndFindPositions(entities, sectionText.getSearchableText(), null, null);
Entity correctEntity = getEntityOnCorrectPosition(entitiesWithPositions, toFindPositions);
if (correctEntity == null) {
log.warn("Could not calculate surrounding text");
return Pair.of("", "");
}
if (sectionText.getCellStarts() != null && !sectionText.getCellStarts().isEmpty()) {
surroundingWordsService.addSurroundingText(Set.of(correctEntity), sectionText.getSearchableText(), null, sectionText.getCellStarts());
} else {
surroundingWordsService.addSurroundingText(Set.of(correctEntity), sectionText.getSearchableText(), null);
}
return Pair.of(correctEntity.getTextBefore(), correctEntity.getTextAfter());
}
private boolean sectionContainsEntry(SectionArea sectionArea, List<Rectangle> positions) {
for (Rectangle position : positions) {
if (sectionArea.contains(position)) {
return true;
}
}
return false;
}
private Entity getEntityOnCorrectPosition(Set<Entity> entitiesWithPositions, List<Rectangle> toFindPositions) {
for (Entity entityWithPos : entitiesWithPositions) {
for (EntityPositionSequence entityPositionSequence : entityWithPos.getPositionSequences()) {
for (TextPositionSequence textPositionSequence : entityPositionSequence.getSequences()) {
for (Rectangle manualRedactionRectangle : toFindPositions) {
if (intersects(manualRedactionRectangle, textPositionSequence.getRectangle())) {
return entityWithPos;
}
}
}
}
}
return null;
}
public boolean intersects(Rectangle manualPosition,
com.iqser.red.service.redaction.v1.model.Rectangle textPositionRectangle) {
return textPositionRectangle.getTopLeft()
.getX() + textPositionRectangle.getWidth() > manualPosition.getTopLeftX() && textPositionRectangle.getTopLeft()
.getY() + textPositionRectangle.getHeight() > manualPosition.getTopLeftY() && textPositionRectangle.getTopLeft()
.getX() < manualPosition.getTopLeftX() + manualPosition.getWidth() && textPositionRectangle.getTopLeft()
.getY() < manualPosition.getTopLeftY() + manualPosition.getHeight();
}
}
@@ -1,53 +0,0 @@
package com.iqser.red.service.redaction.v1.server.redaction.service;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileType;
import com.iqser.red.service.redaction.v1.server.client.EntityRecognitionClient;
import com.iqser.red.service.redaction.v1.server.client.model.EntityRecognitionRequest;
import com.iqser.red.service.redaction.v1.server.client.model.EntityRecognitionSection;
import com.iqser.red.service.redaction.v1.server.settings.RedactionServiceSettings;
import com.iqser.red.service.redaction.v1.server.storage.RedactionStorageService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Service;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class NerAnalyserService {
private final RedactionStorageService redactionStorageService;
private final EntityRecognitionClient entityRecognitionClient;
private final RedactionServiceSettings redactionServiceSettings;
public void computeNerEntities(String dossierId, String fileId) {
if (redactionServiceSettings.isEnableEntityRecognition()) {
var text = redactionStorageService.getText(dossierId, fileId);
long start = System.currentTimeMillis();
if (text != null) {
var nerRequest = EntityRecognitionRequest.builder()
.data(text.getSectionTexts()
.stream()
.map(sectionText -> new EntityRecognitionSection(sectionText.getSectionNumber(), new String(Base64
.encodeBase64(sectionText
.getText().getBytes()))))
.collect(Collectors.toList()))
.build();
var nerResponse = entityRecognitionClient.findAuthors(nerRequest);
log.info("Computing NER entities took: {} ms for dossierId {} and fileId {}", System.currentTimeMillis() - start, dossierId, fileId);
redactionStorageService.storeObject(dossierId, fileId, FileType.NER_ENTITIES, nerResponse);
} else {
log.warn("Warning, text for file: {} in dossier: {} is null", fileId, dossierId);
}
}
}
}
@@ -1,25 +1,16 @@
package com.iqser.red.service.redaction.v1.server.redaction.service;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import com.iqser.red.service.redaction.v1.model.Change;
import com.iqser.red.service.redaction.v1.model.ChangeType;
import com.iqser.red.service.redaction.v1.model.RedactionLog;
import com.iqser.red.service.redaction.v1.model.RedactionLogChanges;
import com.iqser.red.service.redaction.v1.model.RedactionLogEntry;
import com.iqser.red.service.redaction.v1.model.*;
import com.iqser.red.service.redaction.v1.server.storage.RedactionStorageService;
import io.micrometer.core.annotation.Timed;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.OffsetDateTime;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Service
@@ -29,7 +20,8 @@ public class RedactionChangeLogService {
private final RedactionStorageService redactionStorageService;
public RedactionLogChanges computeChanges(String dossierId, String fileId, RedactionLog currentRedactionLog) {
@Timed("redactmanager_computeChanges")
public RedactionLogChanges computeChanges(String dossierId, String fileId, RedactionLog currentRedactionLog, int analysisNumber) {
long start = System.currentTimeMillis();
@@ -37,24 +29,23 @@ public class RedactionChangeLogService {
if (previousRedactionLog == null) {
currentRedactionLog.getRedactionLogEntry().forEach(entry -> {
entry.getChanges().add(new Change(ChangeType.ADDED, OffsetDateTime.now()));
entry.getChanges().add(new Change(analysisNumber, ChangeType.ADDED, OffsetDateTime.now()));
});
return new RedactionLogChanges(currentRedactionLog, false);
}
List<RedactionLogEntry> notRemovedPreviousEntries = previousRedactionLog.getRedactionLogEntry()
.stream()
.filter(entry -> !entry.getChanges()
.get(entry.getChanges().size() - 1)
.getType()
.equals(ChangeType.REMOVED))
.filter(entry -> !entry.lastChangeIsRemoved())
.collect(Collectors.toList());
Set<RedactionLogEntry> added = new HashSet<>(currentRedactionLog.getRedactionLogEntry());
added.removeAll(notRemovedPreviousEntries);
Set<RedactionLogEntry> added = currentRedactionLog.getRedactionLogEntry().stream()
.filter(entry -> entry.getChanges().isEmpty() || !entry.lastChangeIsRemoved()).collect(Collectors.toSet());
notRemovedPreviousEntries.forEach(added::remove);
Set<RedactionLogEntry> removed = new HashSet<>(notRemovedPreviousEntries);
removed.removeAll(currentRedactionLog.getRedactionLogEntry());
currentRedactionLog.getRedactionLogEntry().forEach(removed::remove);
Map<String, RedactionLogEntry> addedIds = new HashMap<>();
added.forEach(entry -> {
@@ -72,16 +63,16 @@ public class RedactionChangeLogService {
newRedactionLogEntries.forEach(entry -> {
if (removedIds.contains(entry.getId()) && addedIds.containsKey(entry.getId())) {
List<Change> changes = entry.getChanges();
changes.add(new Change(ChangeType.CHANGED, OffsetDateTime.now()));
changes.add(new Change(analysisNumber, ChangeType.CHANGED, OffsetDateTime.now()));
var newEntry = addedIds.get(entry.getId());
newEntry.setChanges(changes);
addedIds.put(entry.getId(), newEntry);
toRemove.add(entry);
} else if (removedIds.contains(entry.getId())) {
entry.getChanges().add(new Change(ChangeType.REMOVED, OffsetDateTime.now()));
entry.getChanges().add(new Change(analysisNumber, ChangeType.REMOVED, OffsetDateTime.now()));
} else if (addedIds.containsKey(entry.getId())) {
List<Change> changes = entry.getChanges();
changes.add(new Change(ChangeType.ADDED, OffsetDateTime.now()));
changes.add(new Change(analysisNumber, ChangeType.ADDED, OffsetDateTime.now()));
var newEntry = addedIds.get(entry.getId());
newEntry.setChanges(changes);
addedIds.put(entry.getId(), newEntry);
@@ -92,8 +83,8 @@ public class RedactionChangeLogService {
newRedactionLogEntries.removeAll(toRemove);
addedIds.forEach((k, v) -> {
if(v.getChanges().isEmpty()) {
v.getChanges().add(new Change(ChangeType.ADDED, OffsetDateTime.now()));
if (v.getChanges().isEmpty()) {
v.getChanges().add(new Change(analysisNumber, ChangeType.ADDED, OffsetDateTime.now()));
}
newRedactionLogEntries.add(v);
});
@@ -1,5 +1,15 @@
package com.iqser.red.service.redaction.v1.server.redaction.service;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import com.iqser.red.service.redaction.v1.model.Point;
import com.iqser.red.service.redaction.v1.model.Rectangle;
import com.iqser.red.service.redaction.v1.model.RedactionLogEntry;
@@ -7,24 +17,26 @@ import com.iqser.red.service.redaction.v1.server.parsing.model.RedTextPosition;
import com.iqser.red.service.redaction.v1.server.parsing.model.TextPositionSequence;
import com.iqser.red.service.redaction.v1.server.redaction.model.Entity;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityPositionSequence;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
import com.iqser.red.service.redaction.v1.server.redaction.model.Image;
import com.iqser.red.service.redaction.v1.server.redaction.model.PageEntities;
import com.iqser.red.service.redaction.v1.server.redaction.utils.IdBuilder;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
import io.micrometer.core.annotation.Timed;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
@RequiredArgsConstructor
public class RedactionLogCreatorService {
private final DictionaryService dictionaryService;
public List<RedactionLogEntry> createRedactionLog(PageEntities pageEntities, int numberOfPages, String dossierTemplateId) {
@Timed("redactmanager_createRedactionLog")
public List<RedactionLogEntry> createRedactionLog(PageEntities pageEntities, int numberOfPages,
String dossierTemplateId) {
List<RedactionLogEntry> entries = new ArrayList<>();
@@ -61,7 +73,6 @@ public class RedactionLogCreatorService {
.legalBasis(image.getLegalBasis())
.matchedRule(image.getMatchedRule())
.isHint(dictionaryService.isHint(image.getType(), dossierTemplateId))
.manual(false)
.isDictionaryEntry(false)
.isRecommendation(false)
.positions(List.of(new Rectangle(new Point((float) image.getPosition()
@@ -83,7 +94,7 @@ public class RedactionLogCreatorService {
List<RedactionLogEntry> redactionLogEntities = new ArrayList<>();
// Duplicates can exist due table extraction colums over multiple rows.
// Duplicates can exist due table extraction columns over multiple rows.
Set<String> processedIds = new HashSet<>();
entityLoop:
@@ -129,16 +140,27 @@ public class RedactionLogCreatorService {
if (textPositions.size() == 1) {
rectangles.add(TextPositionSequence.fromData(textPositions, page).getRectangle());
} else {
float x = textPositions.get(0).getXDirAdj();
float y = textPositions.get(0).getYDirAdj();
float width = textPositions.get(0).getWidthDirAdj();
float height = textPositions.get(0).getHeightDir();
int startIndex = 0;
for (int i = 1; i < textPositions.size(); i++) {
float xDirAdj = textPositions.get(i).getXDirAdj();
float yDirAdj = textPositions.get(i).getYDirAdj();
if (yDirAdj != y) {
float widthDir = textPositions.get(i).getWidthDirAdj();
float heightDir = textPositions.get(i).getHeightDir();
if (!(isCharInSameLine(y, yDirAdj, height, heightDir) && isCharClose(x, xDirAdj, width))) {
rectangles.add(TextPositionSequence.fromData(textPositions.subList(startIndex, i), page)
.getRectangle());
y = yDirAdj;
width = widthDir;
height = heightDir;
startIndex = i;
}
x = xDirAdj;
}
if (startIndex != textPositions.size()) {
rectangles.add(TextPositionSequence.fromData(textPositions.subList(startIndex, textPositions.size()), page)
@@ -150,6 +172,27 @@ public class RedactionLogCreatorService {
}
private boolean isCharClose(float x, float xDirAdj, float width) {
float max = x + (5 * width);
return xDirAdj < max;
}
private boolean isCharInSameLine(float y, float yCompare, float height, float heightCompare) {
float offsetHeight = height / 2;
float minHeight = height - offsetHeight;
float maxHeight = height + offsetHeight;
float offsetY = height / 10;
float minY = y - offsetY;
float maxY = y + offsetY;
return yCompare > minY && yCompare < maxY && heightCompare > minHeight && heightCompare < maxHeight;
}
private RedactionLogEntry createRedactionLogEntry(Entity entity, String dossierTemplateId) {
Set<String> referenceIds = new HashSet<>();
@@ -163,7 +206,8 @@ public class RedactionLogCreatorService {
.type(entity.getType())
.redacted(entity.isRedaction())
.isHint(isHint(entity.getType(), dossierTemplateId))
.isRecommendation(isRecommendation(entity.getType(), dossierTemplateId))
.isRecommendation(entity.getEntityType().equals(EntityType.RECOMMENDATION))
.isFalsePositive(entity.getEntityType().equals(EntityType.FALSE_POSITIVE) || entity.getEntityType().equals(EntityType.FALSE_RECOMMENDATION))
.section(entity.getHeadline())
.sectionNumber(entity.getSectionNumber())
.matchedRule(entity.getMatchedRule())
@@ -193,11 +237,4 @@ public class RedactionLogCreatorService {
return dictionaryService.isHint(type, dossierTemplateId);
}
private boolean isRecommendation(String type, String dossierTemplateId) {
return dictionaryService.isRecommendation(type, dossierTemplateId);
}
}
@@ -1,23 +1,44 @@
package com.iqser.red.service.redaction.v1.server.redaction.service;
import java.awt.Color;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.*;
import org.springframework.stereotype.Service;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.AnnotationStatus;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.Comment;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.ManualRedactions;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.IdRemoval;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.ManualForceRedaction;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.ManualImageRecategorization;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.ManualLegalBasisChange;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.ManualRedactionEntry;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.entitymapped.ManualResizeRedaction;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.configuration.Colors;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileType;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.type.Type;
import com.iqser.red.service.redaction.v1.model.ManualChange;
import com.iqser.red.service.redaction.v1.model.ManualRedactionType;
import com.iqser.red.service.redaction.v1.model.Point;
import com.iqser.red.service.redaction.v1.model.Rectangle;
import com.iqser.red.service.redaction.v1.model.RedactionLog;
import com.iqser.red.service.redaction.v1.model.RedactionLogComment;
import com.iqser.red.service.redaction.v1.model.RedactionLogEntry;
import com.iqser.red.service.redaction.v1.server.redaction.utils.IdBuilder;
import com.iqser.red.service.redaction.v1.model.RedactionRequest;
import com.iqser.red.service.redaction.v1.model.SectionArea;
import com.iqser.red.service.redaction.v1.model.SectionGrid;
import com.iqser.red.service.redaction.v1.server.exception.NotFoundException;
import com.iqser.red.service.redaction.v1.server.storage.RedactionStorageService;
import io.micrometer.core.annotation.Timed;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
@@ -28,16 +49,58 @@ import lombok.extern.slf4j.Slf4j;
@RequiredArgsConstructor
public class RedactionLogMergeService {
private final DictionaryService dictionaryService;
private static final String DELETED_TYPE_COLOR = "#9398a0";
private final SectionTextService sectionTextService;
private final RedactionStorageService redactionStorageService;
public RedactionLog mergeRedactionLogData(RedactionLog redactionLog, String dossierTemplateId,
ManualRedactions manualRedactions, Set<Integer> excludedPages) {
@Timed("redactmanager_getMergedRedactionLog")
public RedactionLog provideRedactionLog(RedactionRequest redactionRequest) {
log.debug("Requested preview for: {}", redactionRequest);
var redactionLog = redactionStorageService.getRedactionLog(redactionRequest.getDossierId(), redactionRequest.getFileId());
if (redactionLog == null) {
throw new NotFoundException("RedactionLog not present");
}
SectionGrid sectionGrid = redactionStorageService.getSectionGrid(redactionRequest.getDossierId(), redactionRequest.getFileId());
if (sectionGrid.getSections().isEmpty()) {
log.info("SectionGrid does not have headlines set. Computing headlines now!");
var text = redactionStorageService.getText(redactionRequest.getDossierId(), redactionRequest.getFileId());
// enhance section grid with headline data
for (var sectionText : text.getSectionTexts()) {
sectionGrid.getSections()
.add(new SectionGrid.SectionGridSection(sectionText.getSectionNumber(), sectionText.getHeadline(), sectionText.getSectionAreas()
.stream()
.map(SectionArea::getPage)
.collect(Collectors.toSet()), sectionText.getSectionAreas()));
}
redactionStorageService.storeObject(redactionRequest.getDossierId(), redactionRequest.getFileId(), FileType.SECTION_GRID, sectionGrid);
}
log.debug("Loaded redaction log with computationalVersion: {}", redactionLog.getAnalysisVersion());
var merged = mergeRedactionLogData(redactionLog, sectionGrid, redactionRequest.getManualRedactions(), redactionRequest.getExcludedPages(), redactionRequest.getTypes(), redactionRequest.getColors());
merged.getRedactionLogEntry().removeIf(e -> e.isFalsePositive() && !redactionRequest.isIncludeFalsePositives());
return merged;
}
private RedactionLog mergeRedactionLogData(RedactionLog redactionLog, SectionGrid sectionGrid,
ManualRedactions manualRedactions, Set<Integer> excludedPages,
List<Type> types, Colors colors) {
var skippedImportedRedactions = new HashSet<>();
log.info("Merging Redaction log with manual redactions");
if (manualRedactions != null) {
var manualRedactionLogEntries = addManualAddEntries(manualRedactions.getEntriesToAdd(), manualRedactions.getComments(), dossierTemplateId);
var manualRedactionLogEntries = addManualAddEntries(sectionGrid, manualRedactions.getEntriesToAdd(), manualRedactions.getComments(), colors, types);
redactionLog.getRedactionLogEntry().addAll(manualRedactionLogEntries);
@@ -47,13 +110,17 @@ public class RedactionLogMergeService {
processRedactionLogEntry(manualRedactionWrappers.stream()
.filter(mr -> entry.getId().equals(mr.getId()))
.collect(Collectors.toList()), dossierTemplateId, entry);
.collect(Collectors.toList()), entry, types, colors);
entry.setComments(manualRedactions.getComments().get(entry.getId()));
if (entry.isImported() && !entry.isRedacted()) {
skippedImportedRedactions.add(entry.getId());
}
entry.setComments(convert(manualRedactions.getComments().get(entry.getId())));
if (excludedPages != null && !excludedPages.isEmpty()) {
entry.getPositions().forEach(pos -> {
if (excludedPages.contains(pos.getPage())) {
if (!entry.isLocalManualRedaction() && excludedPages.contains(pos.getPage())) {
entry.setExcluded(true);
}
});
@@ -62,6 +129,24 @@ public class RedactionLogMergeService {
}
Set<String> processedIds = new HashSet<>();
redactionLog.getRedactionLogEntry().removeIf(entry -> {
if (entry.getImportedRedactionIntersections() != null) {
entry.getImportedRedactionIntersections().removeAll(skippedImportedRedactions);
if (!entry.getImportedRedactionIntersections()
.isEmpty() && (!entry.isImage() || entry.isImage() && !(entry.getType()
.equals("image") || entry.getType().equals("ocr")))) {
return true;
}
}
if (processedIds.contains(entry.getId())) {
log.info("Duplicate annotation found with id {}", entry.getId());
return true;
}
processedIds.add(entry.getId());
return false;
});
return redactionLog;
}
@@ -94,6 +179,12 @@ public class RedactionLogMergeService {
}
});
manualRedactions.getResizeRedactions().forEach(item -> {
if (item.getSoftDeletedTime() == null) {
manualRedactionWrappers.add(new ManualRedactionWrapper(item.getAnnotationId(), item.getRequestDate(), item));
}
});
Collections.sort(manualRedactionWrappers);
return manualRedactionWrappers;
@@ -101,7 +192,7 @@ public class RedactionLogMergeService {
private void processRedactionLogEntry(List<ManualRedactionWrapper> manualRedactionWrappers,
String dossierTemplateId, RedactionLogEntry redactionLogEntry) {
RedactionLogEntry redactionLogEntry, List<Type> types, Colors colors) {
manualRedactionWrappers.forEach(mrw -> {
@@ -109,75 +200,88 @@ public class RedactionLogMergeService {
var imageRecategorization = (ManualImageRecategorization) mrw.getItem();
String manualOverrideReason = null;
if (imageRecategorization.getStatus().equals(AnnotationStatus.APPROVED)) {
redactionLogEntry.setStatus(AnnotationStatus.APPROVED);
redactionLogEntry.setType(imageRecategorization.getType());
redactionLogEntry.setSection("Image:" + redactionLogEntry.getType());
if (isHint(types, imageRecategorization.getType())) {
redactionLogEntry.setRedacted(false);
redactionLogEntry.setHint(true);
} else {
redactionLogEntry.setHint(false);
}
manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", recategorized by manual override");
} else if (imageRecategorization.getStatus().equals(AnnotationStatus.REQUESTED)) {
manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", requested to recategorize");
redactionLogEntry.setStatus(AnnotationStatus.REQUESTED);
redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), dossierTemplateId, false, redactionLogEntry
.isRedacted(), false));
redactionLogEntry.setRecategorizationType(imageRecategorization.getType());
} else {
redactionLogEntry.setStatus(AnnotationStatus.DECLINED);
redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, false, redactionLogEntry.isRedacted(), false, types));
}
redactionLogEntry.setManualRedactionUserId(imageRecategorization.getUser());
redactionLogEntry.setReason(manualOverrideReason);
redactionLogEntry.setManual(true);
redactionLogEntry.setManualRedactionType(ManualRedactionType.RECATEGORIZE);
if(manualOverrideReason != null) {
redactionLogEntry.setReason(manualOverrideReason);
}
redactionLogEntry.getManualChanges()
.add(ManualChange.from(imageRecategorization)
.withManualRedactionType(ManualRedactionType.RECATEGORIZE)
.withChange("type", imageRecategorization.getType()));
}
if (mrw.getItem() instanceof IdRemoval) {
var manualRemoval = (IdRemoval) mrw.getItem();
String manualOverrideReason = null;
if (manualRemoval.getStatus().equals(AnnotationStatus.APPROVED)) {
redactionLogEntry.setRedacted(false);
redactionLogEntry.setStatus(AnnotationStatus.APPROVED);
manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", removed by manual override");
redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), dossierTemplateId, false, redactionLogEntry
.isRedacted(), true));
} else if (manualRemoval.getStatus().equals(AnnotationStatus.REQUESTED)) {
manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", requested to remove");
redactionLogEntry.setStatus(AnnotationStatus.REQUESTED);
redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), dossierTemplateId, true, redactionLogEntry
.isRedacted(), false));
if (manualRemoval.getStatus()
.equals(AnnotationStatus.APPROVED) && manualRemoval.isRemoveFromDictionary()) {
log.debug("Skipping merge for dictionary-modifying entry");
} else {
redactionLogEntry.setStatus(AnnotationStatus.DECLINED);
}
String manualOverrideReason = null;
if (manualRemoval.getStatus().equals(AnnotationStatus.APPROVED)) {
redactionLogEntry.setRedacted(false);
manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", removed by manual override");
redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, false, redactionLogEntry.isRedacted(), true, types));
redactionLogEntry.setHint(false);
} else if (manualRemoval.getStatus().equals(AnnotationStatus.REQUESTED)) {
manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", requested to remove");
redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, true, redactionLogEntry.isRedacted(), false, types));
}
redactionLogEntry.setReason(manualOverrideReason);
redactionLogEntry.setManual(true);
redactionLogEntry.setManualRedactionUserId(manualRemoval.getUser());
redactionLogEntry.setManualRedactionType(ManualRedactionType.REMOVE);
redactionLogEntry.setDictionaryEntry(manualRemoval.isRemoveFromDictionary());
redactionLogEntry.setDossierDictionaryEntry(manualRemoval.isRemoveFromDictionary());
if(manualOverrideReason != null) {
redactionLogEntry.setReason(manualOverrideReason);
}
redactionLogEntry.getManualChanges()
.add(ManualChange.from(manualRemoval)
.withManualRedactionType(manualRemoval.isRemoveFromDictionary() ? ManualRedactionType.REMOVE_FROM_DICTIONARY : ManualRedactionType.REMOVE_LOCALLY));
}
}
if (mrw.getItem() instanceof ManualForceRedaction) {
var manualForceRedact = (ManualForceRedaction) mrw.getItem();
String manualOverrideReason = null;
var dictionaryIsHint = isHint(types, redactionLogEntry.getType());
if (manualForceRedact.getStatus().equals(AnnotationStatus.APPROVED)) {
redactionLogEntry.setRedacted(true);
redactionLogEntry.setStatus(AnnotationStatus.APPROVED);
redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), dossierTemplateId, false, redactionLogEntry
.isRedacted(), false));
// Forcing a skipped hint should result in a hint
if (dictionaryIsHint) {
redactionLogEntry.setHint(true);
} else {
redactionLogEntry.setRedacted(true);
}
redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, false, redactionLogEntry.isRedacted(), false, types));
manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", forced by manual override");
redactionLogEntry.setLegalBasis(manualForceRedact.getLegalBasis());
} else if (manualForceRedact.getStatus().equals(AnnotationStatus.REQUESTED)) {
manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", requested to force redact");
redactionLogEntry.setStatus(AnnotationStatus.REQUESTED);
redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), dossierTemplateId, true, redactionLogEntry
.isRedacted(), false));
manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", requested to force " + (dictionaryIsHint ? "hint" : "redact"));
redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, true, redactionLogEntry.isRedacted(), false, types));
redactionLogEntry.setLegalBasis(manualForceRedact.getLegalBasis());
} else {
redactionLogEntry.setStatus(AnnotationStatus.DECLINED);
}
redactionLogEntry.setManualRedactionUserId(manualForceRedact.getUser());
redactionLogEntry.setReason(manualOverrideReason);
redactionLogEntry.setManual(true);
redactionLogEntry.setManualRedactionType(ManualRedactionType.FORCE_REDACT);
if(manualOverrideReason != null) {
redactionLogEntry.setReason(manualOverrideReason);
}
var manualChange = ManualChange.from(manualForceRedact)
.withManualRedactionType(dictionaryIsHint ? ManualRedactionType.FORCE_HINT : ManualRedactionType.FORCE_REDACT);
redactionLogEntry.getManualChanges().add(manualChange);
}
@@ -185,23 +289,67 @@ public class RedactionLogMergeService {
var manualLegalBasisChange = (ManualLegalBasisChange) mrw.getItem();
String manualOverrideReason = null;
if (manualLegalBasisChange.getStatus().equals(AnnotationStatus.APPROVED)) {
redactionLogEntry.setStatus(AnnotationStatus.APPROVED);
manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", legal basis was manually changed");
redactionLogEntry.setLegalBasis(manualLegalBasisChange.getLegalBasis());
redactionLogEntry.setRedacted(true);
if (manualLegalBasisChange.getSection() != null) {
redactionLogEntry.setSection(manualLegalBasisChange.getSection());
}
if (redactionLogEntry.isRectangle() && manualLegalBasisChange.getValue() != null) {
redactionLogEntry.setValue(manualLegalBasisChange.getValue());
}
} else if (manualLegalBasisChange.getStatus().equals(AnnotationStatus.REQUESTED)) {
manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", legal basis change requested");
redactionLogEntry.setStatus(AnnotationStatus.REQUESTED);
redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), dossierTemplateId, true, redactionLogEntry
.isRedacted(), false));
redactionLogEntry.setLegalBasisChangeValue(manualLegalBasisChange.getLegalBasis());
} else {
redactionLogEntry.setStatus(AnnotationStatus.DECLINED);
redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, true, redactionLogEntry.isRedacted(), false, types));
}
if(manualOverrideReason != null) {
redactionLogEntry.setReason(manualOverrideReason);
}
var manualChange = ManualChange.from(manualLegalBasisChange)
.withManualRedactionType(ManualRedactionType.LEGAL_BASIS_CHANGE);
manualChange.withChange("legalBasis", manualLegalBasisChange.getLegalBasis());
if (manualLegalBasisChange.getSection() != null) {
manualChange.withChange("section", manualLegalBasisChange.getSection());
}
if (redactionLogEntry.isRectangle() && manualLegalBasisChange.getValue() != null) {
manualChange.withChange("value", manualLegalBasisChange.getValue());
}
redactionLogEntry.getManualChanges().add(manualChange);
}
if (mrw.getItem() instanceof ManualResizeRedaction) {
var manualResizeRedact = (ManualResizeRedaction) mrw.getItem();
String manualOverrideReason = null;
if (manualResizeRedact.getStatus().equals(AnnotationStatus.APPROVED)) {
redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, false, redactionLogEntry.isRedacted(), false, types));
redactionLogEntry.setPositions(convertPositions(manualResizeRedact.getPositions()));
redactionLogEntry.setValue(manualResizeRedact.getValue());
// This is for backwards compatibility, now the text after/before is calculated during reanalysis because we need to find dict entries on positions where entries are resized to smaller.
if (manualResizeRedact.getTextBefore() != null || manualResizeRedact.getTextAfter() != null) {
redactionLogEntry.setTextBefore(manualResizeRedact.getTextBefore());
redactionLogEntry.setTextAfter(manualResizeRedact.getTextAfter());
}
manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", resized by manual override");
} else if (manualResizeRedact.getStatus().equals(AnnotationStatus.REQUESTED)) {
manualOverrideReason = mergeReasonIfNecessary(redactionLogEntry.getReason(), ", requested to resize redact");
redactionLogEntry.setColor(getColor(redactionLogEntry.getType(), colors, true, redactionLogEntry.isRedacted(), false, types));
redactionLogEntry.setPositions(convertPositions(manualResizeRedact.getPositions()));
// This is for backwards compatibility, now the text after/before is calculated during reanalysis because we need to find dict entries on positions where entries are resized to smaller.
if (manualResizeRedact.getTextBefore() != null || manualResizeRedact.getTextAfter() != null) {
redactionLogEntry.setTextBefore(manualResizeRedact.getTextBefore());
redactionLogEntry.setTextAfter(manualResizeRedact.getTextAfter());
}
}
redactionLogEntry.setManualRedactionUserId(manualLegalBasisChange.getUser());
redactionLogEntry.setReason(manualOverrideReason);
redactionLogEntry.setManual(true);
redactionLogEntry.setManualRedactionType(ManualRedactionType.LEGAL_BASIS_CHANGE);
redactionLogEntry.getManualChanges()
.add(ManualChange.from(manualResizeRedact)
.withManualRedactionType(ManualRedactionType.RESIZE)
.withChange("value", manualResizeRedact.getValue()));
}
});
@@ -222,17 +370,23 @@ public class RedactionLogMergeService {
}
public List<RedactionLogEntry> addManualAddEntries(Set<ManualRedactionEntry> manualAdds,
Map<String, List<Comment>> comments, String dossierTemplateId) {
public List<RedactionLogEntry> addManualAddEntries(SectionGrid sectionGrid, Set<ManualRedactionEntry> manualAdds,
Map<String, List<Comment>> comments, Colors colors,
List<Type> types) {
List<RedactionLogEntry> redactionLogEntries = new ArrayList<>();
for (ManualRedactionEntry manualRedactionEntry : manualAdds) {
if (!approvedAndShouldBeInDictionary(manualRedactionEntry)) {
RedactionLogEntry redactionLogEntry = createRedactionLogEntry(manualRedactionEntry, manualRedactionEntry.getAnnotationId(), dossierTemplateId);
if (shouldCreateManualEntry(manualRedactionEntry)) {
RedactionLogEntry redactionLogEntry = createRedactionLogEntry(manualRedactionEntry, manualRedactionEntry.getAnnotationId(), colors, types);
redactionLogEntry.setPositions(convertPositions(manualRedactionEntry.getPositions()));
redactionLogEntry.setComments(comments.get(manualRedactionEntry.getAnnotationId()));
redactionLogEntry.setComments(convert(comments.get(manualRedactionEntry.getAnnotationId())));
redactionLogEntry.setTextBefore(manualRedactionEntry.getTextBefore());
redactionLogEntry.setTextAfter(manualRedactionEntry.getTextAfter());
sectionTextService.handleSectionText(sectionGrid, redactionLogEntry);
redactionLogEntries.add(redactionLogEntry);
}
}
@@ -240,70 +394,126 @@ public class RedactionLogMergeService {
return redactionLogEntries;
}
private List<Rectangle> convertPositions(List<com.iqser.red.service.persistence.service.v1.api.model.annotations.Rectangle> positions){
return positions.stream().map(pos -> new Rectangle(new Point(pos.getTopLeftX(), pos.getTopLeftY()), pos.getWidth(), pos
.getHeight(), pos.getPage())).collect(Collectors.toList());
private List<RedactionLogComment> convert(List<Comment> comments) {
return comments == null ? null : comments.stream()
.map(c -> new RedactionLogComment(c.getId(), c.getUser(), c.getText(), c.getAnnotationId(), c.getFileId(), c.getDate(), c.getSoftDeletedTime()))
.collect(Collectors.toList());
}
private boolean approvedAndShouldBeInDictionary(ManualRedactionEntry manualRedactionEntry) {
private List<Rectangle> convertPositions(
List<com.iqser.red.service.persistence.service.v1.api.model.annotations.Rectangle> positions) {
return manualRedactionEntry.getStatus()
.equals(AnnotationStatus.APPROVED) && (manualRedactionEntry.isAddToDictionary() || manualRedactionEntry.isAddToDossierDictionary());
return positions.stream()
.map(pos -> new Rectangle(new Point(pos.getTopLeftX(), pos.getTopLeftY()), pos.getWidth(), pos.getHeight(), pos.getPage()))
.collect(Collectors.toList());
}
@SuppressWarnings("PMD.UselessParentheses")
private boolean shouldCreateManualEntry(ManualRedactionEntry manualRedactionEntry) {
return (!manualRedactionEntry.isAddToDictionary() && !manualRedactionEntry.isAddToDossierDictionary()) || ((manualRedactionEntry.isAddToDictionary() || manualRedactionEntry.isAddToDossierDictionary()) && manualRedactionEntry.getProcessedDate() == null);
}
private RedactionLogEntry createRedactionLogEntry(ManualRedactionEntry manualRedactionEntry, String id,
String dossierTemplateId) {
Colors colors, List<Type> types) {
var addToDictionary = manualRedactionEntry.isAddToDictionary() || manualRedactionEntry.isAddToDossierDictionary();
var change = ManualChange.from(manualRedactionEntry)
.withManualRedactionType(addToDictionary ? ManualRedactionType.ADD_TO_DICTIONARY : ManualRedactionType.ADD_LOCALLY);
List<ManualChange> changeList = new ArrayList<>();
changeList.add(change);
return RedactionLogEntry.builder()
.id(id)
.color(getColorForManualAdd(manualRedactionEntry.getType(), dossierTemplateId, manualRedactionEntry.getStatus()))
.color(getColorForManualAdd(manualRedactionEntry.getType(), colors, manualRedactionEntry.getStatus(), types))
.reason(manualRedactionEntry.getReason())
.isDictionaryEntry(manualRedactionEntry.isAddToDictionary())
.isDossierDictionaryEntry(manualRedactionEntry.isAddToDossierDictionary())
.legalBasis(manualRedactionEntry.getLegalBasis())
.value(manualRedactionEntry.getValue())
.sourceId(manualRedactionEntry.getSourceId())
.section(manualRedactionEntry.getSection())
.type(manualRedactionEntry.getType())
.redacted(true)
.isHint(false)
.section(null)
.sectionNumber(-1)
.manual(true)
.status(manualRedactionEntry.getStatus())
.manualRedactionType(ManualRedactionType.ADD)
.manualRedactionUserId(manualRedactionEntry.getUser())
.rectangle(manualRedactionEntry.isRectangle())
.manualChanges(changeList)
.build();
}
private float[] getColor(String type, String dossierTemplateId, boolean requested, boolean isRedaction,
boolean skipped) {
private float[] getColor(String type, Colors colors, boolean requested, boolean isRedaction, boolean skipped,
List<Type> types) {
if (requested) {
return dictionaryService.getRequestRemoveColor(dossierTemplateId);
return convertColor(colors.getRequestRemoveColor());
}
if (skipped || !isRedaction && !dictionaryService.isHint(type, dossierTemplateId)) {
return dictionaryService.getNotRedactedColor(dossierTemplateId);
if (skipped || !isRedaction && !isHint(types, type)) {
return convertColor(colors.getSkippedColor());
}
return dictionaryService.getColor(type, dossierTemplateId);
return getColor(types, type);
}
private float[] getColorForManualAdd(String type, String dossierTemplateId, AnnotationStatus status) {
private float[] getColorForManualAdd(String type, Colors colors, AnnotationStatus status, List<Type> types) {
if (status.equals(AnnotationStatus.REQUESTED)) {
return dictionaryService.getRequestAddColor(dossierTemplateId);
return convertColor(colors.getRequestAddColor());
} else if (status.equals(AnnotationStatus.DECLINED)) {
return dictionaryService.getNotRedactedColor(dossierTemplateId);
return convertColor(colors.getSkippedColor());
}
return getColor(type, dossierTemplateId);
return getColor(types, type);
}
private float[] getColor(String type, String dossierTemplateId) {
private float[] getColor(List<Type> types, String type) {
return dictionaryService.getColor(type, dossierTemplateId);
var matchingTypes = getMatchingTypes(types, type);
Optional<Type> foundAndNotDeletedType = matchingTypes.stream().filter(t -> !isDeletedType(t)).findFirst();
if (foundAndNotDeletedType.isPresent()) {
return convertColor(foundAndNotDeletedType.get().getHexColor());
}
Optional<Type> firstDeletedType = matchingTypes.stream().findFirst();
return firstDeletedType.map(value -> convertColor(value.getHexColor()))
.orElseGet(() -> convertColor(DELETED_TYPE_COLOR));
}
boolean isHint(List<Type> types, String type) {
var matchingTypes = getMatchingTypes(types, type);
Optional<Type> foundAndNotDeletedType = matchingTypes.stream().filter(t -> !isDeletedType(t)).findFirst();
if (foundAndNotDeletedType.isPresent()) {
return foundAndNotDeletedType.get().isHint();
}
Optional<Type> firstDeletedType = matchingTypes.stream().findFirst();
return firstDeletedType.map(Type::isHint).orElse(false);
}
private float[] convertColor(String hex) {
Color color = Color.decode(hex);
return new float[]{color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f};
}
private List<Type> getMatchingTypes(List<Type> types, String type) {
return types.stream().filter(t -> t.getType().equals(type)).collect(Collectors.toList());
}
private boolean isDeletedType(Type type) {
return type.getSoftDeletedTime() != null;
}
@@ -324,6 +534,5 @@ public class RedactionLogMergeService {
}
}
@@ -1,10 +1,5 @@
package com.iqser.red.service.redaction.v1.server.redaction.service;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.iqser.red.service.redaction.v1.model.CellRectangle;
import com.iqser.red.service.redaction.v1.model.Point;
import com.iqser.red.service.redaction.v1.model.SectionRectangle;
@@ -14,8 +9,11 @@ import com.iqser.red.service.redaction.v1.server.classification.model.TextBlock;
import com.iqser.red.service.redaction.v1.server.tableextraction.model.AbstractTextContainer;
import com.iqser.red.service.redaction.v1.server.tableextraction.model.Cell;
import com.iqser.red.service.redaction.v1.server.tableextraction.model.Table;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
@RequiredArgsConstructor
@@ -48,7 +46,7 @@ public class SectionGridCreatorService {
.getRectanglesPerPage()
.computeIfAbsent(page, (x) -> new ArrayList<>())
.add(new SectionRectangle(new Point(textBlock.getMinX(), textBlock.getMinY()), textBlock.getWidth(), textBlock
.getHeight(), i + 1, paragraph.getPageBlocks().size()));
.getHeight(), i + 1, paragraph.getPageBlocks().size(),null));
} else if (textBlock instanceof Table) {
@@ -1,35 +1,19 @@
package com.iqser.red.service.redaction.v1.server.redaction.service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import com.iqser.red.service.redaction.v1.model.Point;
import com.iqser.red.service.redaction.v1.model.SectionArea;
import com.iqser.red.service.redaction.v1.server.classification.model.Document;
import com.iqser.red.service.redaction.v1.server.classification.model.Footer;
import com.iqser.red.service.redaction.v1.server.classification.model.Header;
import com.iqser.red.service.redaction.v1.server.classification.model.Paragraph;
import com.iqser.red.service.redaction.v1.server.classification.model.SectionText;
import com.iqser.red.service.redaction.v1.server.classification.model.TextBlock;
import com.iqser.red.service.redaction.v1.server.classification.model.UnclassifiedText;
import com.iqser.red.service.redaction.v1.server.redaction.model.CellValue;
import com.iqser.red.service.redaction.v1.server.redaction.model.Image;
import com.iqser.red.service.redaction.v1.server.redaction.model.ImageType;
import com.iqser.red.service.redaction.v1.server.redaction.model.PdfImage;
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
import com.iqser.red.service.redaction.v1.server.classification.model.*;
import com.iqser.red.service.redaction.v1.server.redaction.model.*;
import com.iqser.red.service.redaction.v1.server.tableextraction.model.Cell;
import com.iqser.red.service.redaction.v1.server.tableextraction.model.Table;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@Slf4j
@Service
@@ -95,7 +79,7 @@ public class SectionTextBuilderService {
.get(0)
.getSequences()
.get(0)
.getPage());
.getPage(), null);
sectionText.getSectionAreas().add(sectionArea);
sectionText.getTextBlocks().addAll(cell.getTextBlocks());
@@ -157,7 +141,7 @@ public class SectionTextBuilderService {
.get(0)
.getSequences()
.get(0)
.getPage());
.getPage(), null);
sectionText.getTextBlocks().addAll(cell.getTextBlocks());
sectionText.getSectionAreas().add(sectionArea);
@@ -187,11 +171,11 @@ public class SectionTextBuilderService {
SectionText sectionText = new SectionText();
for (TextBlock paragraphTextBlock : paragraphTextBlocks) {
SectionArea sectionArea = new SectionArea(new Point(paragraphTextBlock.getMinX(), paragraphTextBlock.getMinY()), paragraphTextBlock
.getWidth(), paragraphTextBlock.getHeight(), paragraphTextBlock.getPage());
.getWidth(), paragraphTextBlock.getHeight(), paragraphTextBlock.getPage(), null);
sectionText.getSectionAreas().add(sectionArea);
}
sectionText.setText(searchableText.toString());
sectionText.setText(searchableText.asString());
sectionText.setHeadline(headline);
sectionText.setSectionNumber(sectionNumber.intValue());
sectionText.setTable(false);
@@ -0,0 +1,39 @@
package com.iqser.red.service.redaction.v1.server.redaction.service;
import com.iqser.red.service.redaction.v1.model.RedactionLogEntry;
import com.iqser.red.service.redaction.v1.model.SectionGrid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class SectionTextService {
public void handleSectionText(SectionGrid sectionGrid, RedactionLogEntry redactionLogEntry) {
if (redactionLogEntry.getSection() != null) {
// set by UI
return;
}
if (sectionGrid != null) {
var firstPosition = !redactionLogEntry.getPositions().isEmpty() ? redactionLogEntry.getPositions().iterator().next() : null;
if (firstPosition != null) {
for (var section : sectionGrid.getSections()) {
if (section.getPages().contains(firstPosition.getPage())) {
for (var sectionArea : section.getSectionAreas()) {
if (sectionArea.contains(firstPosition)) {
redactionLogEntry.setSection(section.getHeadline());
redactionLogEntry.setSectionNumber(section.getSectionNumber());
return;
}
}
}
}
}
}
}
}
@@ -4,6 +4,8 @@ import com.iqser.red.service.redaction.v1.server.redaction.model.Dictionary;
import com.iqser.red.service.redaction.v1.server.redaction.model.Entity;
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
import com.iqser.red.service.redaction.v1.server.settings.RedactionServiceSettings;
import io.micrometer.core.annotation.Timed;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -19,6 +21,7 @@ public class SurroundingWordsService {
private final RedactionServiceSettings redactionServiceSettings;
@Timed("redactmanager_addSurroundingText")
public void addSurroundingText(Set<Entity> entities, SearchableText searchableText, Dictionary dictionary) {
if (entities.isEmpty()) {
@@ -28,10 +31,10 @@ public class SurroundingWordsService {
try {
for (Entity entity : entities) {
if (dictionary.isHint(entity.getType())) {
if (dictionary != null && dictionary.isHint(entity.getType())) {
continue;
}
findSurroundingWords(entity, searchableText.toString(), entity.getStart(), entity.getEnd());
findSurroundingWords(entity, searchableText.asString(), entity.getStart(), entity.getEnd());
}
} catch (Exception e) {
log.warn("Could not get surrounding text!");
@@ -39,6 +42,7 @@ public class SurroundingWordsService {
}
@Timed("redactmanager_addSurroundingText_tables")
public void addSurroundingText(Set<Entity> entities, SearchableText searchableText, Dictionary dictionary,
List<Integer> cellstarts) {
@@ -47,7 +51,7 @@ public class SurroundingWordsService {
}
try {
String searchableString = searchableText.toString();
String searchableString = searchableText.asString();
if (cellstarts != null) {
for (int i = 0; i < cellstarts.size(); i++) {
@@ -64,7 +68,7 @@ public class SurroundingWordsService {
String text = searchableString.substring(startOffset, endOffset);
for (Entity entity : entities) {
if (dictionary.isHint(entity.getType())) {
if (dictionary != null && dictionary.isHint(entity.getType())) {
continue;
}
@@ -1,88 +1,75 @@
package com.iqser.red.service.redaction.v1.server.redaction.utils;
import com.iqser.red.service.redaction.v1.model.Engine;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.AnnotationStatus;
import com.iqser.red.service.persistence.service.v1.api.model.annotations.ManualRedactions;
import com.iqser.red.service.redaction.v1.server.redaction.model.Dictionary;
import com.iqser.red.service.redaction.v1.server.redaction.model.DictionaryIncrementValue;
import com.iqser.red.service.redaction.v1.server.redaction.model.DictionaryModel;
import com.iqser.red.service.redaction.v1.server.redaction.model.Entity;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityPositionSequence;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
import com.iqser.red.service.redaction.v1.server.redaction.model.Image;
import com.iqser.red.service.redaction.v1.server.redaction.model.SearchableText;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Slf4j
@UtilityClass
@SuppressWarnings("PMD")
public class EntitySearchUtils {
public boolean sectionContainsAny(String sectionText, Set<DictionaryIncrementValue> values) {
String inputString = sectionText.toLowerCase(Locale.ROOT);
for (DictionaryIncrementValue value : values) {
String cleanValue = value.getValue().toLowerCase(Locale.ROOT).trim();
if (cleanValue.length() <= 2) {
continue;
}
int startIndex;
int stopIndex = 0;
do {
startIndex = inputString.indexOf(cleanValue, stopIndex);
stopIndex = startIndex + cleanValue.length();
if (startIndex > -1 && (startIndex == 0 || Character.isWhitespace(inputString.charAt(startIndex - 1)) || isSeparator(inputString
.charAt(startIndex - 1))) && (stopIndex == inputString.length() || isSeparator(inputString.charAt(stopIndex)))) {
return true;
}
} while (startIndex > -1);
}
return false;
public boolean sectionContainsAny(String sectionText, SearchImplementation searchImplementation) {
return searchImplementation.atLeastOneMatches(sectionText);
}
public Set<Entity> findEntities(String inputString, SearchImplementation searchImplementation, DictionaryModel type, FindEntityDetails details) {
public Set<Entity> find(String inputString, Set<String> values, String type, String headline, int sectionNumber,
boolean isDictionaryEntry, boolean isDossierDictionary, Engine engine) {
Set<Entity> found = find(inputString, searchImplementation, details);
Set<Entity> found = new HashSet<>();
for (String value : values) {
String cleanValue = value.trim();
if (cleanValue.length() <= 2) {
continue;
}
int startIndex;
int stopIndex = 0;
do {
startIndex = inputString.indexOf(cleanValue, stopIndex);
stopIndex = startIndex + cleanValue.length();
if (startIndex > -1 && (startIndex == 0 || Character.isWhitespace(inputString.charAt(startIndex - 1)) || isSeparator(inputString
.charAt(startIndex - 1))) && (stopIndex == inputString.length() || isSeparator(inputString.charAt(stopIndex)))) {
found.add(new Entity(inputString.substring(startIndex, stopIndex), type, startIndex, stopIndex, headline, sectionNumber, isDictionaryEntry, isDossierDictionary, engine));
}
} while (startIndex > -1);
if (details.getEntityType() == EntityType.RECOMMENDATION) {
Set<Entity> falseRecommendations = find(inputString, type.getFalseRecommendationsSearch(), details.withEntityType(EntityType.FALSE_RECOMMENDATION));
removeFalsePositives(found, falseRecommendations);
found.addAll(falseRecommendations);
} else {
Set<Entity> falsePositives = find(inputString, type.getFalsePositiveSearch(), details.withEntityType(EntityType.FALSE_POSITIVE));
removeFalsePositives(found, falsePositives);
found.addAll(falsePositives);
}
return found;
}
public Set<Entity> find(String inputString, SearchImplementation searchImplementation, FindEntityDetails findEntityDetails) {
Set<Entity> entities = new HashSet<>();
private boolean isSeparator(char c) {
searchImplementation.getMatches(inputString).forEach(match -> validateAndAddEntity(entities, findEntityDetails, inputString, match.getStartIndex(), match.getEndIndex()));
return Character.isWhitespace(c) || Pattern.matches("\\p{Punct}", String.valueOf(c)) || c == '\"' || c == '‘' || c == '’';
return entities;
}
private void validateAndAddEntity(Set<Entity> entities, FindEntityDetails findEntityDetails, String inputString, int startIndex, int stopIndex) {
if ((startIndex == 0 || SeparatorUtils.isSeparator(inputString.charAt(startIndex - 1)))
&& (stopIndex == inputString.length() || SeparatorUtils.isSeparator(inputString.charAt(stopIndex)))) {
entities.add(new Entity(inputString.substring(startIndex, stopIndex), findEntityDetails.getType(), startIndex, stopIndex,
findEntityDetails.getHeadline(), findEntityDetails.getSectionNumber(), findEntityDetails.isDictionaryEntry(),
findEntityDetails.isDossierDictionary(), findEntityDetails.getEngine(), findEntityDetails.getEntityType()));
}
}
public Set<Entity> clearAndFindPositions(Set<Entity> entities, SearchableText text, Dictionary dictionary) {
public Set<Entity> clearAndFindPositions(Set<Entity> entities, SearchableText text, Dictionary dictionary, ManualRedactions manualRedactions) {
Map<String, List<Entity>> entitiesByWord = new HashMap<>();
@@ -92,13 +79,10 @@ public class EntitySearchUtils {
for (String word : entitiesByWord.keySet()) {
List<Entity> orderedEntities = entitiesByWord.get(word)
.stream()
.sorted(Comparator.comparing(Entity::getStart))
.collect(Collectors.toList());
List<Entity> orderedEntities = entitiesByWord.get(word).stream().sorted(Comparator.comparing(Entity::getStart)).collect(Collectors.toList());
Entity firstEntity = orderedEntities.get(0);
List<EntityPositionSequence> positionSequences = text.getSequences(firstEntity.getWord()
.trim(), dictionary.isCaseInsensitiveDictionary(firstEntity.getType()), firstEntity.getTargetSequences());
.trim(), dictionary == null || dictionary.isCaseInsensitiveDictionary(firstEntity.getType()), firstEntity.getTargetSequences());
for (int i = 0; i <= orderedEntities.size() - 1; i++) {
try {
@@ -109,25 +93,109 @@ public class EntitySearchUtils {
}
}
if (manualRedactions != null && manualRedactions.getResizeRedactions() != null && !manualRedactions.getResizeRedactions().isEmpty()){
applyResizeRedactions(entities, manualRedactions);
}
removeEntitiesContainedInLarger(entities);
return entities;
}
private void applyResizeRedactions(Set<Entity> entitiesWithPositions, ManualRedactions manualRedactions) {
if (manualRedactions == null || manualRedactions.getResizeRedactions() == null || manualRedactions.getResizeRedactions().isEmpty()) {
return;
}
entitiesWithPositions.forEach(e -> e.getPositionSequences()
.forEach(pos -> manualRedactions.getResizeRedactions().stream().filter(resize -> resize.getStatus().equals(AnnotationStatus.APPROVED)).forEach(resize -> {
if (resize.getAnnotationId().equals(pos.getId())) {
if (resize.getValue().length() < e.getWord().length() && e.getWord().contains(resize.getValue())) {
int start = e.getWord().indexOf(resize.getValue());
e.setStart(e.getStart() + start);
e.setEnd(e.getStart() + resize.getValue().length());
e.setResized(true);
e.setWord(resize.getValue());
} else if (resize.getValue().length() > e.getWord().length() && resize.getValue().contains(e.getWord())) {
int start = resize.getValue().indexOf(e.getWord());
e.setStart(e.getStart() - start);
e.setEnd(e.getStart() + resize.getValue().length());
e.setResized(true);
e.setWord(resize.getValue());
}
}
})));
}
public void removeFalsePositives(Set<Entity> entities, Set<Entity> falsePositives) {
List<Entity> wordsToRemove = new ArrayList<>();
for (Entity word : falsePositives) {
for (Entity inner : entities) {
if (inner.getWord().length() < word.getWord()
.length() && inner.getStart() >= word.getStart() && inner.getEnd() <= word.getEnd() && word != inner && word.getSectionNumber() == inner.getSectionNumber()) {
wordsToRemove.add(inner);
}
}
}
wordsToRemove.forEach(entities::remove);
entities.removeAll(falsePositives);
}
public void removeEntitiesContainedInLarger(Set<Entity> entities) {
List<Entity> wordsToRemove = new ArrayList<>();
for (Entity word : entities) {
for (Entity inner : entities) {
if (inner.getWord().length() < word.getWord()
.length() && inner.getStart() >= word.getStart() && inner.getEnd() <= word.getEnd() && word != inner && word
.getSectionNumber() == inner.getSectionNumber()) {
wordsToRemove.add(inner);
.length() && inner.getStart() >= word.getStart() && inner.getEnd() <= word.getEnd() && word != inner && word.getSectionNumber() == inner.getSectionNumber()) {
if (word.getEntityType().equals(EntityType.RECOMMENDATION) && inner.getEntityType().equals(EntityType.ENTITY)) {
wordsToRemove.add(word);
} else if(!(inner.getEntityType() == EntityType.FALSE_RECOMMENDATION && word.getEntityType() == EntityType.ENTITY ||
inner.getEntityType() == EntityType.ENTITY && word.getEntityType() == EntityType.FALSE_RECOMMENDATION)) {
if(inner.isResized()){
wordsToRemove.add(word);
} else {
wordsToRemove.add(inner);
}
}
}
}
}
entities.removeAll(wordsToRemove);
wordsToRemove.stream().filter(e -> !e.getEntityType().equals(EntityType.FALSE_POSITIVE) && !e.getEntityType().equals(EntityType.FALSE_RECOMMENDATION)).forEach(entities::remove);
}
public static void removeEntitiesContainedInRedactedLogos(Map<Integer, Set<Image>> imagesPerPage, Map<Integer, List<Entity>> entitiesPerPage) {
if (entitiesPerPage != null && imagesPerPage != null) {
for (int page : imagesPerPage.keySet()) {
Set<Entity> removeEntitiesFromPage = new HashSet<>();
if (!entitiesPerPage.containsKey(page)) {
continue;
}
imagesPerPage.get(page).forEach(image -> {
if (image.isRedaction()) {
entitiesPerPage.get(page).forEach(entity -> {
if (isImageOverlappingEntity(image, entity)) {
log.info("Logo overlaps entity and entity will be removed");
removeEntitiesFromPage.add(entity);
}
});
}
});
entitiesPerPage.get(page).removeAll(removeEntitiesFromPage);
}
}
}
@@ -141,16 +209,26 @@ public class EntitySearchUtils {
if (entities.contains(found)) {
Optional<Entity> existingOptional = entities.stream().filter(entity -> entity.equals(found)).findFirst();
if(!existingOptional.isPresent()){
if (existingOptional.isEmpty()) {
return;
}
var existing = existingOptional.get();
if (dictionary.getDictionaryRank(existing.getType()) <= dictionary.getDictionaryRank(found.getType())) {
if (existing.getType().equals(found.getType())) {
existing.getEngines().addAll(found.getEngines());
existing.setLegalBasis(found.getLegalBasis());
existing.setMatchedRule(found.getMatchedRule());
existing.setRedactionReason(found.getRedactionReason());
if (existing.getEntityType().equals(EntityType.RECOMMENDATION) && found.getEntityType().equals(EntityType.ENTITY)
|| existing.getEntityType().equals(EntityType.ENTITY) && found.getEntityType().equals(EntityType.RECOMMENDATION)) {
existing.setEntityType(EntityType.ENTITY);
if (found.isRedaction()) {
existing.setRedaction(true);
}
}
} else if (dictionary.getDictionaryRank(existing.getType()) <= dictionary.getDictionaryRank(found.getType())) {
entities.remove(found);
entities.add(found);
} else {
existing.getEngines().addAll(found.getEngines());
}
} else {
entities.add(found);
@@ -165,20 +243,77 @@ public class EntitySearchUtils {
}
public void addOrAddEngine(Set<Entity> existing, Set<Entity> toBeAdded){
private boolean isImageOverlappingEntity(Image image, Entity entity) {
for(Entity toAdd: toBeAdded){
return image.getPosition() != null && entity.getPositionSequences() != null && entity.getPositionSequences().get(0) != null && entity.getPositionSequences()
.get(0)
.getSequences() != null && entity.getPositionSequences().get(0).getSequences().get(0) != null && image.getPosition().getX() < entity.getPositionSequences()
.get(0)
.getSequences()
.get(0)
.getX1() && image.getPosition().getX() + image.getPosition().getWidth() > entity.getPositionSequences().get(0).getSequences().get(0).getX2() && image.getPosition()
.getY() < entity.getPositionSequences().get(0).getSequences().get(0).getY1() && image.getPosition().getY() + image.getPosition()
.getHeight() > entity.getPositionSequences().get(0).getSequences().get(0).getY2();
}
public Set<Entity> findNonOverlappingMatchEntities(Set<Entity> existingEntities, Set<Entity> foundEntities) {
Set<Entity> result = new HashSet<>();
if (existingEntities != null && foundEntities != null) {
for (Entity foundEntity : foundEntities) {
if (!overlaps(existingEntities, foundEntity)) {
result.add(foundEntity);
}
}
}
return result;
}
public void addOrAddEngine(Set<Entity> existing, Set<Entity> toBeAdded) {
for (Entity toAdd : toBeAdded) {
if (existing.contains(toAdd)) {
Optional<Entity> existingOptional = existing.stream().filter(entity -> entity.equals(toAdd)).findFirst();
if(!existingOptional.isPresent()){
Optional<Entity> existingOptional = existing.stream().filter(entity -> entity.equals(toAdd)).findFirst();
if (existingOptional.isEmpty()) {
return;
}
var existingEntity = existingOptional.get();
existingEntity.getEngines().addAll(toAdd.getEngines());
existingEntity.setLegalBasis(toAdd.getLegalBasis());
existingEntity.setMatchedRule(toAdd.getMatchedRule());
existingEntity.setRedactionReason(toAdd.getRedactionReason());
} else {
existing.add(toAdd);
}
}
}
/*
* returns true if the found entity overlaps with an existing entity in a way
* that neither entity is a subset of the other
*/
private boolean overlaps(Set<Entity> existingEntities, Entity found) {
for (Entity existing : existingEntities) {
// skip if either start or end is equal
if (existing.getStart().equals(found.getStart()) || existing.getEnd().equals(found.getEnd())) {
continue;
}
for (int i = existing.getStart(); i <= existing.getEnd(); i++) {
for (int j = found.getStart(); j <= found.getEnd(); j++) {
if (i == j) {
return true;
}
}
}
}
return false;
}
}
@@ -0,0 +1,24 @@
package com.iqser.red.service.redaction.v1.server.redaction.utils;
import com.iqser.red.service.redaction.v1.model.Engine;
import com.iqser.red.service.redaction.v1.server.redaction.model.EntityType;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class FindEntityDetails {
private String type;
private String headline;
private int sectionNumber;
private boolean isDictionaryEntry;
private boolean isDossierDictionary;
private Engine engine;
private EntityType entityType;
public FindEntityDetails withEntityType(EntityType entityType) {
this.entityType = entityType;
return this;
}
}
@@ -0,0 +1,92 @@
package com.iqser.red.service.redaction.v1.server.redaction.utils;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.ahocorasick.trie.Trie;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Data
public class SearchImplementation {
private boolean ignoreCase;
private List<String> values;
private Pattern pattern;
private Trie trie;
public SearchImplementation(String value, boolean ignoreCase) {
this.values = List.of(value);
this.ignoreCase = ignoreCase;
this.createSearchImplementation();
}
public SearchImplementation(Collection<String> values, boolean ignoreCase) {
this.values = new ArrayList<>(values);
this.ignoreCase = ignoreCase;
this.createSearchImplementation();
}
private void createSearchImplementation() {
if (this.values.isEmpty()) {
return;
}
if (this.values.size() == 1) {
var text = this.values.iterator().next();
this.pattern = Pattern.compile(Pattern.quote(ignoreCase ? text.toLowerCase(Locale.ROOT) : text));
} else {
var builder = Trie.builder();
if (this.ignoreCase) {
builder.ignoreCase();
}
builder.addKeywords(this.values);
this.trie = builder.build();
}
}
public boolean atLeastOneMatches(String text) {
String textToCheck = text;
if (this.values.isEmpty()) {
return false;
}
if (this.pattern != null) {
if (ignoreCase) {
textToCheck = textToCheck.toLowerCase(Locale.ROOT);
}
return this.pattern.matcher(textToCheck).results().findAny().isPresent();
} else {
return this.trie.containsMatch(textToCheck);
}
}
public List<MatchPosition> getMatches(String text) {
String textToCheck = text;
if (this.values.isEmpty()) {
return new ArrayList<>();
}
if (this.pattern != null) {
if (ignoreCase) {
textToCheck = textToCheck.toLowerCase(Locale.ROOT);
}
return this.pattern.matcher(textToCheck).results().map(r -> new MatchPosition(r.start(), r.end())).collect(Collectors.toList());
} else {
return this.trie.parseText(textToCheck).stream().map(r -> new MatchPosition(r.getStart(), r.getEnd() + 1)).collect(Collectors.toList());
}
}
@Data
@AllArgsConstructor
public static class MatchPosition {
private int startIndex;
private int endIndex;
}
}

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