Compare commits

...
Author SHA1 Message Date
Thomas Beyer 142e8cf957 RED-4875 - update version of pdftron-logic-commons to newest 2023-03-17 17:25:52 +01:00
Thomas Beyer 143538fa40 RED-4875 - call logic of new repo pdftron-logic-commons instead of local one 2023-03-17 10:33:48 +01:00
Christoph Schabert 74a094b42d Change Tagging 2023-03-13 09:41:59 +01:00
Timo Bejan 8ab4092046 Pull request #18: RED-6162 - bumped version
Merge in RED/ocr-service from RED-6162 to master

* commit 'c86cabf70a9ec90687c3f123768f2293da49c4ff':
  RED-6162 - bumped version
  RED-6162 - bumped version
2023-03-10 21:45:38 +01:00
Timo Bejan c86cabf70a RED-6162 - bumped version 2023-03-10 22:43:35 +02:00
Dominique Eiflaender 936a795797 Pull request #17: RED-4645: Multitenancy for storage
Merge in RED/ocr-service from RED-4645 to master

* commit 'e5df0ec65864667134affad2af2a5118126355ce':
  RED-4645: Multitenancy for storage
2023-03-10 16:42:43 +01:00
deiflaender e5df0ec658 RED-4645: Multitenancy for storage 2023-03-10 16:31:34 +01:00
Timo Bejan 0d9365d020 RED-6162 - bumped version 2023-03-10 15:59:25 +02:00
Timo Bejan 5003970fe1 Pull request #16: RED-6162 - bumped version
Merge in RED/ocr-service from RED-6162 to master

* commit '9c6fa4384a7ea43dfd36c6fcb6639e5127ab92b9':
  RED-6162 - bumped version
2023-03-10 14:56:05 +01:00
Timo Bejan 9c6fa4384a RED-6162 - bumped version 2023-03-10 15:52:37 +02:00
Timo Bejan 444b823cce Pull request #15: RED-6162 Redaction Gateway - Persistence Service Merge Updates
Merge in RED/ocr-service from RED-6162 to master

* commit '6208ff029252884ced189806acd4118cd21530e1':
  RED-6182 - version bump
  RED-6162 - test issue on bamboo
  RED-6162 - test issue on bamboo
  RED-6162 - persistence update - identity test
  RED-6162 - persistence update - reverse dependency cleanup
2023-03-10 11:27:13 +01:00
Timo Bejan 6208ff0292 RED-6182 - version bump 2023-03-10 08:45:48 +02:00
Timo Bejan 5156c560f2 RED-6162 - test issue on bamboo 2023-03-10 08:45:48 +02:00
Timo Bejan c2a7e32789 RED-6162 - test issue on bamboo 2023-03-10 08:45:48 +02:00
Timo Bejan c1cd4d1cab RED-6162 - persistence update - identity test 2023-03-10 08:45:48 +02:00
Timo Bejan da5a1cc042 RED-6162 - persistence update - reverse dependency cleanup 2023-03-10 08:45:48 +02:00
28 changed files with 1150 additions and 778 deletions
@@ -5,27 +5,27 @@ SERVICE_NAME=$1
if [[ "$bamboo_planRepository_branchName" == "master" ]]
then
echo "building on master branch"
branchVersion=$(cat pom.xml | grep -Eo "<version>.*" | sed -s 's|<version>\(.*\)\..*\(-*.*\)</version>|\1|')
echo "branch version is : $branchVersion"
latestVersion=$(semver $( git tag -l $branchVersion.* ) | tail -n1)
echo "latestVersion is : $latestVersion"
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 "newVersion is : $newVersion"
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)
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
echo "dev build with tag ${bamboo_planRepository_1_branch}_${bamboo_buildNumber}"
echo "gitTag=${bamboo_planRepository_1_branch}_${bamboo_buildNumber}" > git.tag
exit 0
fi
+3 -22
View File
@@ -7,15 +7,13 @@
<parent>
<groupId>com.iqser.red.service</groupId>
<artifactId>ocr-service-v1</artifactId>
<version>1.0-SNAPSHOT</version>
<version>3.0-SNAPSHOT</version>
</parent>
<artifactId>ocr-service-api-v1</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<persistence-service.version>1.269.0</persistence-service.version>
<redaction-service.version>3.155.0</redaction-service.version>
<persistence-service.version>2.1.0</persistence-service.version>
<dsljson.version>1.9.9</dsljson.version>
</properties>
@@ -43,26 +41,9 @@
<dependency>
<groupId>com.iqser.red.service</groupId>
<artifactId>persistence-service-api-v1</artifactId>
<exclusions>
<exclusion>
<groupId>com.iqser.red.service</groupId>
<artifactId>redaction-service-api-v1</artifactId>
</exclusion>
</exclusions>
<artifactId>persistence-service-internal-api-v1</artifactId>
<version>${persistence-service.version}</version>
</dependency>
<dependency>
<groupId>com.iqser.red.service</groupId>
<artifactId>redaction-service-api-v1</artifactId>
<exclusions>
<exclusion>
<groupId>com.iqser.red.service</groupId>
<artifactId>persistence-service-api-v1</artifactId>
</exclusion>
</exclusions>
<version>${redaction-service.version}</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,17 @@
package com.iqser.red.service.ocr.v1.api.model;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
public class IdentityTest {
@Test
public void mockTest() {
int i = 1;
assertThat(i).isEqualTo(1);
}
}
+7 -2
View File
@@ -7,11 +7,10 @@
<parent>
<groupId>com.iqser.red.service</groupId>
<artifactId>ocr-service-v1</artifactId>
<version>1.0-SNAPSHOT</version>
<version>3.0-SNAPSHOT</version>
</parent>
<artifactId>ocr-service-server-v1</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
@@ -24,6 +23,12 @@
<groupId>com.iqser.red.commons</groupId>
<artifactId>storage-commons</artifactId>
</dependency>
<dependency>
<groupId>com.iqser.red.commons</groupId>
<artifactId>pdftron-logic-commons</artifactId>
<version>dev_red4875_a3a2a</version>
</dependency>
<dependency>
<groupId>com.iqser.red.commons</groupId>
<artifactId>spring-commons</artifactId>
@@ -10,9 +10,12 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.scheduling.annotation.EnableAsync;
import com.iqser.red.commons.spring.DefaultWebMvcConfiguration;
import com.iqser.red.pdftronlogic.commons.InvisibleElementRemovalService;
import com.iqser.red.service.ocr.v1.server.client.FileStatusProcessingUpdateClient;
import com.iqser.red.service.ocr.v1.server.configuration.MessagingConfiguration;
import com.iqser.red.service.ocr.v1.server.multitenancy.AsyncConfig;
import com.iqser.red.service.ocr.v1.server.multitenancy.MultiTenancyMessagingConfiguration;
import com.iqser.red.service.ocr.v1.server.multitenancy.MultiTenancyWebConfiguration;
import com.iqser.red.service.ocr.v1.server.settings.OcrServiceSettings;
import io.micrometer.core.aop.TimedAspect;
@@ -21,7 +24,7 @@ import io.micrometer.core.instrument.MeterRegistry;
@EnableAsync
@EnableConfigurationProperties(OcrServiceSettings.class)
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class})
@Import({DefaultWebMvcConfiguration.class, MessagingConfiguration.class})
@Import({MultiTenancyWebConfiguration.class, AsyncConfig.class, MessagingConfiguration.class, MultiTenancyMessagingConfiguration.class})
@EnableFeignClients(basePackageClasses = FileStatusProcessingUpdateClient.class)
public class Application {
@@ -42,4 +45,11 @@ public class Application {
return new TimedAspect(registry);
}
@Bean
public InvisibleElementRemovalService invisibleElementRemovalService() {
return new InvisibleElementRemovalService();
}
}
@@ -2,7 +2,7 @@ package com.iqser.red.service.ocr.v1.server.client;
import org.springframework.cloud.openfeign.FeignClient;
import com.iqser.red.service.persistence.service.v1.api.resources.FileStatusProcessingUpdateResource;
import com.iqser.red.service.persistence.service.v1.api.internal.resources.FileStatusProcessingUpdateResource;
@FeignClient(name = "FileStatusProcessingUpdateResource", url = "${persistence-service.url}")
public interface FileStatusProcessingUpdateClient extends FileStatusProcessingUpdateResource {
@@ -0,0 +1,10 @@
package com.iqser.red.service.ocr.v1.server.client;
import org.springframework.cloud.openfeign.FeignClient;
import com.iqser.red.service.persistence.service.v1.api.internal.resources.TenantsResource;
@FeignClient(name = "TenantsResource", url = "${persistence-service.url}")
public interface TenantsClient extends TenantsResource {
}
@@ -1,68 +1,68 @@
package com.iqser.red.service.ocr.v1.server.model;
import static com.iqser.red.service.ocr.v1.server.service.InvisibleElementRemovalService.TOLERANCE;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
import java.util.Deque;
import java.util.LinkedList;
import com.pdftron.pdf.Rect;
import lombok.Data;
import lombok.SneakyThrows;
@Data
public class ClippingPathStack {
private Deque<Area> stack = new LinkedList<>();
@SneakyThrows
public ClippingPathStack(Rect rectangle) {
stack.push(new Area(new Rectangle2D.Double(rectangle.getX1(), rectangle.getY1(), rectangle.getWidth(), rectangle.getHeight()).getBounds2D()));
}
@SneakyThrows
public void intersectClippingPath(GeneralPath path) {
getCurrentClippingPath().intersect(new Area(path));
}
public boolean almostIntersects(double x, double y, double width, double height) {
// To address inconsistencies in the calculation of the bounding box we slightly increase the rectangle
// Height or width are zero for straight lines, even though they are being rendered. Therefore, height or width must be at minimum >0.
double x_with_tolerance = x > 0 ? x - TOLERANCE : x + TOLERANCE;
double y_with_tolerance = y > 0 ? y - TOLERANCE : y + TOLERANCE;
double width_with_tolerance = width + (2 * TOLERANCE);
double height_with_tolerance = height + (2 * TOLERANCE);
return getCurrentClippingPath().intersects(x_with_tolerance, y_with_tolerance, width_with_tolerance, height_with_tolerance);
}
public Area getCurrentClippingPath() {
return stack.peek();
}
public void enterNewGState() {
Area current = stack.peek();
Area cloned = new Area();
cloned.add(current);
stack.push(cloned);
}
public void leaveGState() {
stack.pop();
}
}
//package com.iqser.red.service.ocr.v1.server.model;
//
//import static com.iqser.red.service.ocr.v1.server.service.InvisibleElementRemovalService.TOLERANCE;
//
//import java.awt.geom.Area;
//import java.awt.geom.GeneralPath;
//import java.awt.geom.Rectangle2D;
//import java.util.Deque;
//import java.util.LinkedList;
//
//import com.pdftron.pdf.Rect;
//
//import lombok.Data;
//import lombok.SneakyThrows;
//
//@Data
//public class ClippingPathStack {
//
// private Deque<Area> stack = new LinkedList<>();
//
//
// @SneakyThrows
// public ClippingPathStack(Rect rectangle) {
//
// stack.push(new Area(new Rectangle2D.Double(rectangle.getX1(), rectangle.getY1(), rectangle.getWidth(), rectangle.getHeight()).getBounds2D()));
// }
//
//
// @SneakyThrows
// public void intersectClippingPath(GeneralPath path) {
//
// getCurrentClippingPath().intersect(new Area(path));
// }
//
//
// public boolean almostIntersects(double x, double y, double width, double height) {
// // To address inconsistencies in the calculation of the bounding box we slightly increase the rectangle
// // Height or width are zero for straight lines, even though they are being rendered. Therefore, height or width must be at minimum >0.
//
// double x_with_tolerance = x > 0 ? x - TOLERANCE : x + TOLERANCE;
// double y_with_tolerance = y > 0 ? y - TOLERANCE : y + TOLERANCE;
// double width_with_tolerance = width + (2 * TOLERANCE);
// double height_with_tolerance = height + (2 * TOLERANCE);
// return getCurrentClippingPath().intersects(x_with_tolerance, y_with_tolerance, width_with_tolerance, height_with_tolerance);
// }
//
//
// public Area getCurrentClippingPath() {
//
// return stack.peek();
// }
//
//
// public void enterNewGState() {
//
// Area current = stack.peek();
// Area cloned = new Area();
// cloned.add(current);
// stack.push(cloned);
// }
//
//
// public void leaveGState() {
//
// stack.pop();
// }
//
//}
@@ -1,170 +1,170 @@
package com.iqser.red.service.ocr.v1.server.model;
import static com.iqser.red.service.ocr.v1.server.service.InvisibleElementRemovalService.TOLERANCE;
import java.awt.geom.Rectangle2D;
import com.pdftron.common.PDFNetException;
import com.pdftron.pdf.Element;
import com.pdftron.pdf.Rect;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.experimental.FieldDefaults;
import lombok.experimental.SuperBuilder;
@Getter
@SuperBuilder
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class ElementFeatures {
int elementType;
Rectangle2D boundingBox;
public boolean almostMatches(Element element) throws PDFNetException {
return element.getType() == elementType && //
element.getBBox() != null && //
rectsAlmostMatch(element.getBBox());
}
protected boolean almostEqual(double a, double b) {
return Math.abs(a - b) < TOLERANCE;
}
@SneakyThrows
private boolean rectsAlmostMatch(Rect bBox) {
// To address the inconsistencies in the calculation of the bounding box we check equality with a tolerance
return almostEqual(bBox.getX1(), boundingBox.getX()) && //
almostEqual(bBox.getY1(), boundingBox.getY()) && //
almostEqual(bBox.getWidth(), boundingBox.getWidth()) && //
almostEqual(bBox.getHeight(), boundingBox.getHeight());
}
@EqualsAndHashCode(callSuper = true)
@Getter
@SuperBuilder
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
private static class Text extends ElementFeatures {
String text;
int font;
double fontsize;
@Override
public boolean almostMatches(Element element) throws PDFNetException {
return super.almostMatches(element) && //
text.equals(element.getTextString()) && //
font == element.getGState().getFont().getType() && //
almostEqual(fontsize, element.getGState().getFontSize());
}
}
@EqualsAndHashCode(callSuper = true)
@Getter
@SuperBuilder
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
private static class Path extends ElementFeatures {
boolean isClippingPath;
boolean isClipWindingFill;
boolean isStroked;
boolean isFilled;
boolean isWindingFill;
@Override
public boolean almostMatches(Element element) throws PDFNetException {
return super.almostMatches(element) && //
isClippingPath == element.isClippingPath() && //
isClipWindingFill == element.isClipWindingFill() && //
isStroked == element.isStroked() && //
isFilled == element.isFilled() && //
isWindingFill == element.isWindingFill();
}
}
@EqualsAndHashCode(callSuper = true)
@Getter
@SuperBuilder
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
private static class Image extends ElementFeatures {
int dataSize;
int height;
int width;
int renderingIntent;
int componentNum;
int bitsPerComponent;
@Override
public boolean almostMatches(Element element) throws PDFNetException {
return super.almostMatches(element) && //
dataSize == element.getImageDataSize() && //
height == element.getImageHeight() && //
width == element.getImageWidth() && //
renderingIntent == element.getImageRenderingIntent() && //
componentNum == element.getComponentNum() && //
bitsPerComponent == element.getBitsPerComponent();
}
}
public static ElementFeatures extractFeatures(Element element) throws PDFNetException {
return switch (element.getType()) {
case Element.e_path -> Path.builder()
.elementType(element.getType())
.boundingBox(toRectangle2D(element.getBBox()))
.isClippingPath(element.isClippingPath())
.isClipWindingFill(element.isClipWindingFill())
.isStroked(element.isStroked())
.isFilled(element.isFilled())
.isWindingFill(element.isWindingFill())
.build();
case Element.e_text -> Text.builder()
.elementType(element.getType())
.boundingBox(toRectangle2D(element.getBBox()))
.text(element.getTextString())
.font(element.getGState().getFont().getType())
.fontsize(element.getGState().getFontSize())
.build();
case Element.e_image, Element.e_inline_image -> Image.builder()
.elementType(element.getType())
.boundingBox(toRectangle2D(element.getBBox()))
.dataSize(element.getImageDataSize())
.height(element.getImageHeight())
.width(element.getImageWidth())
.renderingIntent(element.getImageRenderingIntent())
.componentNum(element.getComponentNum())
.bitsPerComponent(element.getBitsPerComponent())
.build();
// This technically should never happen, it's a safetynet
default -> throw new RuntimeException("Feature Extraction is not supported for PDFTron.Element with type: " + element.getType());
};
}
private static Rectangle2D toRectangle2D(Rect rect) throws PDFNetException {
return new Rectangle2D.Double(rect.getX1(), rect.getY1(), rect.getWidth(), rect.getHeight());
}
}
//package com.iqser.red.service.ocr.v1.server.model;
//
//import static com.iqser.red.service.ocr.v1.server.service.InvisibleElementRemovalService.TOLERANCE;
//
//import java.awt.geom.Rectangle2D;
//
//import com.pdftron.common.PDFNetException;
//import com.pdftron.pdf.Element;
//import com.pdftron.pdf.Rect;
//
//import lombok.AccessLevel;
//import lombok.EqualsAndHashCode;
//import lombok.Getter;
//import lombok.SneakyThrows;
//import lombok.experimental.FieldDefaults;
//import lombok.experimental.SuperBuilder;
//
//@Getter
//@SuperBuilder
//@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
//public class ElementFeatures {
//
// int elementType;
// Rectangle2D boundingBox;
//
//
// public boolean almostMatches(Element element) throws PDFNetException {
//
// return element.getType() == elementType && //
// element.getBBox() != null && //
// rectsAlmostMatch(element.getBBox());
// }
//
//
// protected boolean almostEqual(double a, double b) {
//
// return Math.abs(a - b) < TOLERANCE;
// }
//
//
// @SneakyThrows
// private boolean rectsAlmostMatch(Rect bBox) {
// // To address the inconsistencies in the calculation of the bounding box we check equality with a tolerance
//
// return almostEqual(bBox.getX1(), boundingBox.getX()) && //
// almostEqual(bBox.getY1(), boundingBox.getY()) && //
// almostEqual(bBox.getWidth(), boundingBox.getWidth()) && //
// almostEqual(bBox.getHeight(), boundingBox.getHeight());
// }
//
//
// @EqualsAndHashCode(callSuper = true)
// @Getter
// @SuperBuilder
// @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
// private static class Text extends ElementFeatures {
//
// String text;
// int font;
// double fontsize;
//
//
// @Override
// public boolean almostMatches(Element element) throws PDFNetException {
//
// return super.almostMatches(element) && //
// text.equals(element.getTextString()) && //
// font == element.getGState().getFont().getType() && //
// almostEqual(fontsize, element.getGState().getFontSize());
// }
//
// }
//
// @EqualsAndHashCode(callSuper = true)
// @Getter
// @SuperBuilder
// @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
// private static class Path extends ElementFeatures {
//
// boolean isClippingPath;
// boolean isClipWindingFill;
// boolean isStroked;
// boolean isFilled;
// boolean isWindingFill;
//
//
// @Override
// public boolean almostMatches(Element element) throws PDFNetException {
//
// return super.almostMatches(element) && //
// isClippingPath == element.isClippingPath() && //
// isClipWindingFill == element.isClipWindingFill() && //
// isStroked == element.isStroked() && //
// isFilled == element.isFilled() && //
// isWindingFill == element.isWindingFill();
//
// }
//
// }
//
// @EqualsAndHashCode(callSuper = true)
// @Getter
// @SuperBuilder
// @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
// private static class Image extends ElementFeatures {
//
// int dataSize;
// int height;
// int width;
// int renderingIntent;
// int componentNum;
// int bitsPerComponent;
//
//
// @Override
// public boolean almostMatches(Element element) throws PDFNetException {
//
// return super.almostMatches(element) && //
// dataSize == element.getImageDataSize() && //
// height == element.getImageHeight() && //
// width == element.getImageWidth() && //
// renderingIntent == element.getImageRenderingIntent() && //
// componentNum == element.getComponentNum() && //
// bitsPerComponent == element.getBitsPerComponent();
// }
//
// }
//
//
// public static ElementFeatures extractFeatures(Element element) throws PDFNetException {
//
// return switch (element.getType()) {
// case Element.e_path -> Path.builder()
// .elementType(element.getType())
// .boundingBox(toRectangle2D(element.getBBox()))
// .isClippingPath(element.isClippingPath())
// .isClipWindingFill(element.isClipWindingFill())
// .isStroked(element.isStroked())
// .isFilled(element.isFilled())
// .isWindingFill(element.isWindingFill())
// .build();
// case Element.e_text -> Text.builder()
// .elementType(element.getType())
// .boundingBox(toRectangle2D(element.getBBox()))
// .text(element.getTextString())
// .font(element.getGState().getFont().getType())
// .fontsize(element.getGState().getFontSize())
// .build();
// case Element.e_image, Element.e_inline_image -> Image.builder()
// .elementType(element.getType())
// .boundingBox(toRectangle2D(element.getBBox()))
// .dataSize(element.getImageDataSize())
// .height(element.getImageHeight())
// .width(element.getImageWidth())
// .renderingIntent(element.getImageRenderingIntent())
// .componentNum(element.getComponentNum())
// .bitsPerComponent(element.getBitsPerComponent())
// .build();
// // This technically should never happen, it's a safetynet
// default -> throw new RuntimeException("Feature Extraction is not supported for PDFTron.Element with type: " + element.getType());
// };
// }
//
//
// private static Rectangle2D toRectangle2D(Rect rect) throws PDFNetException {
//
// return new Rectangle2D.Double(rect.getX1(), rect.getY1(), rect.getWidth(), rect.getHeight());
// }
//
//}
@@ -1,6 +1,7 @@
package com.iqser.red.service.ocr.v1.server.model;
import com.iqser.red.service.redaction.v1.model.Rectangle;
import com.iqser.red.service.persistence.service.v1.api.shared.model.redactionlog.Rectangle;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -0,0 +1,27 @@
package com.iqser.red.service.ocr.v1.server.multitenancy;
import java.util.concurrent.Executor;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
public class AsyncConfig extends AsyncConfigurerSupport {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(7);
executor.setMaxPoolSize(42);
executor.setQueueCapacity(11);
executor.setThreadNamePrefix("TenantAwareTaskExecutor-");
executor.setTaskDecorator(new TenantAwareTaskDecorator());
executor.initialize();
return executor;
}
}
@@ -0,0 +1,105 @@
package com.iqser.red.service.ocr.v1.server.multitenancy;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Base64;
import javax.annotation.PostConstruct;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import lombok.SneakyThrows;
@Service
public class EncryptionDecryptionService {
@Value("${ocr-service.crypto.key:redaction}")
private String key;
private SecretKey secretKey;
private byte[] iv;
@SneakyThrows
@PostConstruct
protected void postConstruct() {
SecureRandom secureRandom = new SecureRandom();
iv = new byte[12];
secureRandom.nextBytes(iv);
secretKey = generateSecretKey(key, iv);
}
@SneakyThrows
public String encrypt(String strToEncrypt) {
return Base64.getEncoder().encodeToString(encrypt(strToEncrypt.getBytes()));
}
@SneakyThrows
public String decrypt(String strToDecrypt) {
byte[] bytes = Base64.getDecoder().decode(strToDecrypt);
return new String(decrypt(bytes), StandardCharsets.UTF_8);
}
@SneakyThrows
public byte[] encrypt(byte[] data) {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] encryptedData = cipher.doFinal(data);
ByteBuffer byteBuffer = ByteBuffer.allocate(4 + iv.length + encryptedData.length);
byteBuffer.putInt(iv.length);
byteBuffer.put(iv);
byteBuffer.put(encryptedData);
return byteBuffer.array();
}
@SneakyThrows
public byte[] decrypt(byte[] encryptedData) {
ByteBuffer byteBuffer = ByteBuffer.wrap(encryptedData);
int noonceSize = byteBuffer.getInt();
if (noonceSize < 12 || noonceSize >= 16) {
throw new IllegalArgumentException("Nonce size is incorrect. Make sure that the incoming data is an AES encrypted file.");
}
byte[] iv = new byte[noonceSize];
byteBuffer.get(iv);
SecretKey secretKey = generateSecretKey(key, iv);
byte[] cipherBytes = new byte[byteBuffer.remaining()];
byteBuffer.get(cipherBytes);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);
return cipher.doFinal(cipherBytes);
}
@SneakyThrows
public SecretKey generateSecretKey(String password, byte[] iv) {
KeySpec spec = new PBEKeySpec(password.toCharArray(), iv, 65536, 128); // AES-128
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] key = secretKeyFactory.generateSecret(spec).getEncoded();
return new SecretKeySpec(key, "AES");
}
}
@@ -0,0 +1,17 @@
package com.iqser.red.service.ocr.v1.server.multitenancy;
import org.springframework.stereotype.Component;
import feign.RequestInterceptor;
import feign.RequestTemplate;
@Component
public class ForwardTenantInterceptor implements RequestInterceptor {
public static final String TENANT_HEADER_NAME = "X-TENANT-ID";
@Override
public void apply(RequestTemplate template) {
template.header(TENANT_HEADER_NAME, TenantContext.getTenantId());
}
}
@@ -0,0 +1,48 @@
package com.iqser.red.service.ocr.v1.server.multitenancy;
import static com.iqser.red.service.ocr.v1.server.multitenancy.ForwardTenantInterceptor.TENANT_HEADER_NAME;
import org.springframework.amqp.rabbit.config.AbstractRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MultiTenancyMessagingConfiguration {
@Bean
public static BeanPostProcessor multitenancyBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof RabbitTemplate) {
((RabbitTemplate) bean).setBeforePublishPostProcessors(m -> {
m.getMessageProperties().setHeader(TENANT_HEADER_NAME, TenantContext.getTenantId());
return m;
});
} else if (bean instanceof AbstractRabbitListenerContainerFactory) {
((AbstractRabbitListenerContainerFactory<?>) bean).setAfterReceivePostProcessors(m -> {
String tenant = m.getMessageProperties().getHeader(TENANT_HEADER_NAME);
if (tenant != null) {
TenantContext.setTenantId(tenant);
} else {
throw new RuntimeException("No Tenant is set queue message");
}
return m;
});
}
return bean;
}
};
}
}
@@ -0,0 +1,28 @@
package com.iqser.red.service.ocr.v1.server.multitenancy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import com.iqser.red.commons.spring.DefaultWebMvcConfiguration;
@Configuration
public class MultiTenancyWebConfiguration extends DefaultWebMvcConfiguration {
private final TenantInterceptor tenantInterceptor;
@Autowired
public MultiTenancyWebConfiguration(TenantInterceptor tenantInterceptor) {
this.tenantInterceptor = tenantInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addWebRequestInterceptor(tenantInterceptor);
}
}
@@ -0,0 +1,45 @@
package com.iqser.red.service.ocr.v1.server.multitenancy;
import org.springframework.stereotype.Service;
import com.iqser.red.service.ocr.v1.server.client.TenantsClient;
import com.iqser.red.storage.commons.model.AzureStorageConnection;
import com.iqser.red.storage.commons.model.S3StorageConnection;
import com.iqser.red.storage.commons.service.StorageConnectionProvider;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class StorageConnectionProviderImpl implements StorageConnectionProvider {
private final TenantsClient tenantsClient;
private final EncryptionDecryptionService encryptionDecryptionService;
@Override
public AzureStorageConnection getAzureStorageConnection(String tenantId) {
var tenant = tenantsClient.getTenant(tenantId);
return AzureStorageConnection.builder()
.connectionString(encryptionDecryptionService.decrypt(tenant.getAzureStorageConnection().getConnectionString()))
.containerName(tenant.getAzureStorageConnection().getContainerName())
.build();
}
@Override
public S3StorageConnection getS3StorageConnection(String tenantId) {
var tenant = tenantsClient.getTenant(tenantId);
return S3StorageConnection.builder()
.key(tenant.getS3StorageConnection().getKey())
.secret(encryptionDecryptionService.decrypt(tenant.getS3StorageConnection().getSecret()))
.signerType(tenant.getS3StorageConnection().getSignerType())
.bucketName(tenant.getS3StorageConnection().getBucketName())
.region(tenant.getS3StorageConnection().getRegion())
.endpoint(tenant.getS3StorageConnection().getEndpoint())
.build();
}
}
@@ -0,0 +1,23 @@
package com.iqser.red.service.ocr.v1.server.multitenancy;
import org.springframework.core.task.TaskDecorator;
import org.springframework.lang.NonNull;
public class TenantAwareTaskDecorator implements TaskDecorator {
@Override
@NonNull
public Runnable decorate(@NonNull Runnable runnable) {
String tenantId = TenantContext.getTenantId();
return () -> {
try {
TenantContext.setTenantId(tenantId);
runnable.run();
} finally {
TenantContext.setTenantId(null);
}
};
}
}
@@ -0,0 +1,29 @@
package com.iqser.red.service.ocr.v1.server.multitenancy;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public final class TenantContext {
private static InheritableThreadLocal<String> currentTenant = new InheritableThreadLocal<>();
public static void setTenantId(String tenantId) {
log.debug("Setting tenantId to " + tenantId);
currentTenant.set(tenantId);
}
public static String getTenantId() {
return currentTenant.get();
}
public static void clear() {
currentTenant.remove();
}
}
@@ -0,0 +1,35 @@
package com.iqser.red.service.ocr.v1.server.multitenancy;
import org.springframework.stereotype.Component;
import org.springframework.ui.ModelMap;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.WebRequestInterceptor;
@Component
public class TenantInterceptor implements WebRequestInterceptor {
public static final String TENANT_HEADER_NAME = "X-TENANT-ID";
@Override
public void preHandle(WebRequest request) {
if (request.getHeader(TENANT_HEADER_NAME) != null) {
TenantContext.setTenantId(request.getHeader(TENANT_HEADER_NAME));
}
}
@Override
public void postHandle(WebRequest request, ModelMap model) {
TenantContext.clear();
}
@Override
public void afterCompletion(WebRequest request, Exception ex) {
}
}
@@ -7,7 +7,8 @@ import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Service;
import com.iqser.red.service.ocr.v1.server.model.image.ImageServiceResponse;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileType;
import com.iqser.red.service.ocr.v1.server.multitenancy.TenantContext;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileType;
import com.iqser.red.storage.commons.service.StorageService;
import lombok.RequiredArgsConstructor;
@@ -31,39 +32,39 @@ public class FileStorageService {
@SneakyThrows
public byte[] getOriginalFile(String dossierId, String fileId) {
return IOUtils.toByteArray(storageService.getObject(getStorageId(dossierId, fileId, FileType.ORIGIN)).getInputStream());
return IOUtils.toByteArray(storageService.getObject(TenantContext.getTenantId(), getStorageId(dossierId, fileId, FileType.ORIGIN)).getInputStream());
}
@SneakyThrows
public InputStream getOriginalFileAsStream(String dossierId, String fileId) {
return storageService.getObject(getStorageId(dossierId, fileId, FileType.ORIGIN)).getInputStream();
return storageService.getObject(TenantContext.getTenantId(), getStorageId(dossierId, fileId, FileType.ORIGIN)).getInputStream();
}
public void storeOriginalFile(String dossierId, String fileId, InputStream stream) {
storageService.storeObject(getStorageId(dossierId, fileId, FileType.ORIGIN), stream);
storageService.storeObject(TenantContext.getTenantId(), getStorageId(dossierId, fileId, FileType.ORIGIN), stream);
}
public boolean untouchedFileExists(String dossierId, String fileId) {
return storageService.objectExists(getStorageId(dossierId, fileId, FileType.UNTOUCHED));
return storageService.objectExists(TenantContext.getTenantId(), getStorageId(dossierId, fileId, FileType.UNTOUCHED));
}
public void storeUntouchedFile(String dossierId, String fileId, byte[] data) {
storageService.storeObject(getStorageId(dossierId, fileId, FileType.UNTOUCHED), new ByteArrayInputStream(data));
storageService.storeObject(TenantContext.getTenantId(), getStorageId(dossierId, fileId, FileType.UNTOUCHED), new ByteArrayInputStream(data));
}
@SneakyThrows
public ImageServiceResponse getImageServiceResponse(String dossierId, String fileId) {
return storageService.readJSONObject(getStorageId(dossierId, fileId, FileType.IMAGE_INFO), ImageServiceResponse.class);
return storageService.readJSONObject(TenantContext.getTenantId(), getStorageId(dossierId, fileId, FileType.IMAGE_INFO), ImageServiceResponse.class);
}
}
@@ -1,466 +1,466 @@
package com.iqser.red.service.ocr.v1.server.service;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.springframework.stereotype.Service;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.Doubles;
import com.iqser.red.service.ocr.v1.server.model.ClippingPathStack;
import com.iqser.red.service.ocr.v1.server.model.ElementFeatures;
import com.pdftron.common.Matrix2D;
import com.pdftron.common.PDFNetException;
import com.pdftron.pdf.ColorPt;
import com.pdftron.pdf.ColorSpace;
import com.pdftron.pdf.Element;
import com.pdftron.pdf.ElementBuilder;
import com.pdftron.pdf.ElementReader;
import com.pdftron.pdf.ElementWriter;
import com.pdftron.pdf.GState;
import com.pdftron.pdf.PDFDoc;
import com.pdftron.pdf.Page;
import com.pdftron.pdf.PageIterator;
import com.pdftron.pdf.PathData;
import com.pdftron.pdf.Rect;
import com.pdftron.sdf.Obj;
import com.pdftron.sdf.SDFDoc;
import lombok.Builder;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class InvisibleElementRemovalService {
static public final double TOLERANCE = 1e-3;
/**
* Removes all hidden Text, Path and Image Elements from a PDF Document.
* handled cases:
* -Text which is transparent or is set to not render
* -Elements outside of clipping path
* -Elements that have been painted over by visible and filled Paths
* unhandled cases:
* -Elements covered by widely stroked path
* -Elements with the same color as background
* -Any Text set to clipping with its many interactions with other elements
*
* @param pdfFile The PDF file to process
* @param delta If this flag is set only the removed Elements will be written to the output file.
* The Elements are red if they are removed by clipping path, blue for transparency, and a green bounding box for overlap.
* @param out OutputStream to write the resulting file to
**/
@SneakyThrows
public void removeInvisibleElements(InputStream pdfFile, OutputStream out, boolean delta) {
PDFDoc pdfDoc = new PDFDoc(pdfFile);
ElementWriter writer = new ElementWriter();
ElementReader reader = new ElementReader();
Set<Long> visitedXObjIds = new TreeSet<>();
for (PageIterator iterator = pdfDoc.getPageIterator(); iterator.hasNext(); ) {
Page page = iterator.next();
visitedXObjIds.add(page.getSDFObj().getObjNum());
InvisibleElementRemovalContext context = InvisibleElementRemovalContext.builder()
.reader(reader)
.clippingPathStack(new ClippingPathStack(page.getMediaBox()))
.delta(delta)
.overlappedElements(new ArrayList<>())
.visibleElements(new ArrayList<>())
.visitedXObjIds(visitedXObjIds)
.build();
removeClippedElementsAndInvisibleTextAndRememberOverlappedElements(page, writer, context);
context.visitedXObjIds().clear();
removeOverlappedElements(page, writer, context);
}
try {
pdfDoc.save(out, SDFDoc.SaveMode.LINEARIZED, null);
} catch (Exception e) {
log.error("File could not be saved after invisible element removal");
throw new RuntimeException(e);
}
writer.destroy();
reader.destroy();
pdfDoc.close();
}
private void removeClippedElementsAndInvisibleTextAndRememberOverlappedElements(Page page,
ElementWriter writer,
InvisibleElementRemovalContext context) throws PDFNetException {
context.reader().begin(page);
writer.begin(page, ElementWriter.e_replacement, false, true, page.getResourceDict());
processElements(writer, context);
writer.end();
context.reader().end();
}
private void processElements(ElementWriter writer, InvisibleElementRemovalContext context) throws PDFNetException {
for (Element element = context.reader().next(); element != null; element = context.reader().next())
switch (element.getType()) {
case Element.e_image, Element.e_inline_image -> processImages(element, writer, context);
case Element.e_text -> processText(element, writer, context);
case Element.e_path -> processPath(element, writer, context);
case Element.e_form -> processForm(element, writer, context);
case Element.e_group_begin -> {
context.clippingPathStack().enterNewGState();
writer.writeElement(element);
}
case Element.e_group_end -> {
context.clippingPathStack().leaveGState();
writer.writeElement(element);
}
default -> writer.writeElement(element);
}
}
private void processImages(Element imageElement, ElementWriter writer, InvisibleElementRemovalContext context) throws PDFNetException {
Rect rect = imageElement.getBBox();
if (rect == null) {
return;
}
boolean inClippingPath = context.clippingPathStack().almostIntersects(rect.getX1(), rect.getY1(), rect.getWidth(), rect.getHeight());
if (!context.delta() && inClippingPath) {
context.visibleElements().add(ElementFeatures.extractFeatures(imageElement));
}
if (context.delta() ^ inClippingPath) {
writer.writeElement(imageElement);
}
}
private void processText(Element textElement, ElementWriter writer, InvisibleElementRemovalContext context) throws PDFNetException {
Rect rect = textElement.getBBox();
if (rect == null) {
writer.writeElement(textElement);
return;
}
GState gState = textElement.getGState();
boolean inClippingPath = context.clippingPathStack().almostIntersects(rect.getX1(), rect.getY1(), rect.getWidth(), rect.getHeight());
boolean isTextVisible = isTextRenderedVisibly(gState);
if (inClippingPath && isTextVisible) {
context.visibleElements().add(ElementFeatures.extractFeatures(textElement));
}
if (!context.delta()) {
if (inClippingPath && isTextVisible) {
writer.writeElement(textElement);
} else if (textElement.hasTextMatrix()) {
/*
PDFTron Element with type "text" refers to a Tj command. If a Tm command is just above it in the pdf file, PDFTron will join the two commands and treat them as one Element.
hasTextMatrix() checks for this case specifically. Also, Tm changes the position for a whole BT/ET segment, possibly containing multiple Tj commands.
Therefore, the position of a following Tj is affected by not writing the first Element.
This is why, we write only the Tm command:
*/
writer.writeGStateChanges(textElement);
}
} else {
if (!inClippingPath) {
gState.setFillColorSpace(ColorSpace.createDeviceRGB());
// red for elements removed by clipping path
gState.setFillColor(new ColorPt(1, 0, 0));
writer.writeElement(textElement);
}
if (!isTextVisible) {
gState.setFillColorSpace(ColorSpace.createDeviceRGB());
// blue for elements removed due to transparency or not rendered
gState.setFillColor(new ColorPt(0, 0, 1));
gState.setTextRenderMode(GState.e_fill_text);
gState.setFillOpacity(1);
writer.writeElement(textElement);
}
}
}
private void processForm(Element formElement, ElementWriter writer, InvisibleElementRemovalContext context) throws PDFNetException {
writer.writeElement(formElement);
Obj formObj = formElement.getXObject();
if (!context.visitedXObjIds().contains(formObj.getObjNum())) {
context.visitedXObjIds().add(formObj.getObjNum());
// writer needs to be newly initialized when entering a new content stream
// see ElementEditTest in PDFTron (https://www.pdftron.com/documentation/samples/android/java/ElementEditTest)
ElementWriter formWriter = new ElementWriter();
context.reader().formBegin();
formWriter.begin(formObj);
context.reader().clearChangeList();
formWriter.setDefaultGState(context.reader());
processElements(formWriter, context);
formWriter.end();
formWriter.destroy();
context.reader().end();
}
}
private void processPath(Element pathElement, ElementWriter writer, InvisibleElementRemovalContext context) throws PDFNetException {
PathData pathData = pathElement.getPathData();
if (pathData.getOperators().length == 0 && pathData.getPoints().length == 0) {
writer.writeGStateChanges(pathElement);
return;
}
GeneralPath linePath = convertToGeneralPath(pathData);
//transform path to initial user space
var ctm = pathElement.getCTM();
var affineTransform = toAffineTransform(ctm);
linePath.transform(affineTransform);
var rect = linePath.getBounds2D();
boolean inClippingPath = context.clippingPathStack().almostIntersects(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
if (pathElement.isClippingPath()) {
if (pathElement.isClipWindingFill()) {
linePath.setWindingRule(GeneralPath.WIND_NON_ZERO);
} else {
linePath.setWindingRule(GeneralPath.WIND_EVEN_ODD);
}
context.clippingPathStack().intersectClippingPath(linePath);
pathElement.setPathClip(!context.delta());
writer.writeElement(pathElement);
} else {
if (pathElement.isWindingFill()) {
linePath.setWindingRule(GeneralPath.WIND_NON_ZERO);
} else {
linePath.setWindingRule(GeneralPath.WIND_EVEN_ODD);
}
if (inClippingPath) {
if (isFilledAndNonTransparent(pathElement)) {
List<ElementFeatures> currentOverlappedElements = context.visibleElements()
.stream()
.filter(features -> almostContains(linePath, features.getBoundingBox()))
.toList();
context.overlappedElements().addAll(currentOverlappedElements);
context.visibleElements().removeAll(currentOverlappedElements);
}
context.visibleElements().add(ElementFeatures.extractFeatures(pathElement));
if (!context.delta()) {
writer.writeElement(pathElement);
}
}
if (context.delta() && !inClippingPath) {
pathElement.getGState().setFillColorSpace(ColorSpace.createDeviceRGB());
pathElement.getGState().setFillColor(new ColorPt(1, 0, 0));
pathElement.getGState().setStrokeColorSpace(ColorSpace.createDeviceRGB());
pathElement.getGState().setStrokeColor(new ColorPt(1, 0, 0));
writer.writeElement(pathElement);
}
}
}
private void removeOverlappedElements(Page page, ElementWriter writer, InvisibleElementRemovalContext context) throws PDFNetException {
context.reader().begin(page);
writer.begin(page, ElementWriter.e_replacement, false, true, page.getResourceDict());
if (context.delta()) {
// green for element removed due to overlapping
context.overlappedElements().forEach(feature -> drawBBox(writer, feature.getBoundingBox(), "#00FF00"));
context.overlappedElements().clear();
}
processOverlappedElements(writer, context);
writer.end();
context.reader().end();
if (context.overlappedElements().size() > 0) {
log.warn(context.overlappedElements().size() + " overlapped elements have not been found or removed");
}
}
private void processOverlappedElements(ElementWriter writer, InvisibleElementRemovalContext context) throws PDFNetException {
for (Element element = context.reader().next(); element != null; element = context.reader().next()) {
switch (element.getType()) {
case Element.e_form -> processFormOverlappedElements(writer, element, context);
case Element.e_path, Element.e_image, Element.e_inline_image, Element.e_text -> {
boolean anyMatch = false;
for (ElementFeatures elementToRemove : context.overlappedElements()) {
if (elementToRemove.almostMatches(element)) {
context.overlappedElements().remove(elementToRemove);
anyMatch = true;
break;
}
}
if (!anyMatch) {
writer.writeElement(element);
} else if (element.getType() == 3 && element.hasTextMatrix()) {
/*
PDFTron Element with type "text" refers to a Tj command. If a Tm command is just above it in the pdf file, PDFTron will join the two commands and treat them as one Element.
hasTextMatrix() checks for this case specifically. Also, Tm changes the position for a whole BT/ET segment, possibly containing multiple Tj commands.
Therefore, the position of a following Tj is affected by not writing the first Element.
This is why, we write only the Tm command:
*/
writer.writeGStateChanges(element);
}
}
default -> writer.writeElement(element);
}
}
}
private void processFormOverlappedElements(ElementWriter writer, Element formElement, InvisibleElementRemovalContext context) throws PDFNetException {
writer.writeElement(formElement);
Obj formObj = formElement.getXObject();
if (!context.visitedXObjIds().contains(formObj.getObjNum())) {
context.visitedXObjIds().add(formObj.getObjNum());
// writer needs to be newly initialized when entering a new content stream
// see ElementEditTest in PDFTron (https://www.pdftron.com/documentation/samples/android/java/ElementEditTest)
ElementWriter formWriter = new ElementWriter();
context.reader().formBegin();
formWriter.begin(formObj);
context.reader().clearChangeList();
formWriter.setDefaultGState(context.reader());
processOverlappedElements(formWriter, context);
formWriter.end();
formWriter.destroy();
context.reader().end();
}
}
private boolean isTextRenderedVisibly(GState gState) throws PDFNetException {
return gState.getTextRenderMode() != GState.e_invisible_text && //
!(gState.getTextRenderMode() == GState.e_fill_text && gState.getFillOpacity() == 0) && //
!(gState.getTextRenderMode() == GState.e_stroke_text && gState.getStrokeOpacity() == 0) && //
!(gState.getTextRenderMode() == GState.e_fill_stroke_text && gState.getFillOpacity() == 0 && gState.getStrokeOpacity() == 0);
}
private GeneralPath convertToGeneralPath(PathData pathData) throws PDFNetException {
GeneralPath linePath = new GeneralPath();
Iterator<Double> points = Doubles.asList(pathData.getPoints()).iterator();
Iterable<Byte> operators = Bytes.asList(pathData.getOperators());
for (var operator : operators) {
switch (operator) {
case PathData.e_moveto -> linePath.moveTo(points.next(), points.next());
case PathData.e_lineto -> linePath.lineTo(points.next(), points.next());
case PathData.e_cubicto -> linePath.curveTo(points.next(), points.next(), points.next(), points.next(), points.next(), points.next());
case PathData.e_closepath -> linePath.closePath();
case PathData.e_rect -> {
double x = points.next();
double y = points.next();
double w = points.next();
double h = points.next();
linePath.moveTo(x, y);
linePath.lineTo(x + w, y);
linePath.lineTo(x + w, y + h);
linePath.lineTo(x, y + h);
linePath.closePath();
}
default -> throw new PDFNetException("Invalid Element Type", 0, "", "", "");
}
}
return linePath;
}
private boolean almostContains(Shape outer, Rectangle2D inner) {
//To address inconsistencies in the calculation of the bounding box we slightly shrink the inner rectangle
double x_with_tolerance = inner.getX() >= 0 ? inner.getX() + TOLERANCE : inner.getX() - TOLERANCE;
double y_with_tolerance = inner.getY() >= 0 ? inner.getY() + TOLERANCE : inner.getY() - TOLERANCE;
double height_with_tolerance = inner.getHeight() - (2 * TOLERANCE);
double width_with_tolerance = inner.getWidth() - (2 * TOLERANCE);
Rectangle2D innerRect = new Rectangle2D.Double(x_with_tolerance, y_with_tolerance, width_with_tolerance, height_with_tolerance);
return outer.contains(innerRect);
}
private boolean isFilledAndNonTransparent(Element element) throws PDFNetException {
return element.isFilled() && element.getGState().getFillOpacity() == 1;
}
@SneakyThrows
private void drawBBox(ElementWriter writer, Rectangle2D r, String hexcolor) {
ColorPt colorPt = new ColorPt(Integer.valueOf(hexcolor.substring(1, 3), 16) / 255d,
Integer.valueOf(hexcolor.substring(3, 5), 16) / 255d,
Integer.valueOf(hexcolor.substring(5, 7), 16) / 255d);
ElementBuilder eb = new ElementBuilder();
Element rect = eb.createRect(r.getX(), r.getY(), r.getWidth(), r.getHeight());
rect.setPathStroke(true);
rect.getGState().setStrokeColorSpace(ColorSpace.createDeviceRGB());
rect.getGState().setStrokeColor(colorPt);
writer.writePlacedElement(rect);
colorPt.destroy();
eb.destroy();
}
private static AffineTransform toAffineTransform(Matrix2D ctm) throws PDFNetException {
return new AffineTransform(ctm.getA(), ctm.getB(), ctm.getC(), ctm.getD(), ctm.getH(), ctm.getV());
}
@Builder
private record InvisibleElementRemovalContext(
boolean delta,
ElementReader reader,
ClippingPathStack clippingPathStack,
List<ElementFeatures> overlappedElements,
List<ElementFeatures> visibleElements,
Set<Long> visitedXObjIds) {
}
}
//package com.iqser.red.service.ocr.v1.server.service;
//
//import java.awt.Shape;
//import java.awt.geom.AffineTransform;
//import java.awt.geom.GeneralPath;
//import java.awt.geom.Rectangle2D;
//import java.io.InputStream;
//import java.io.OutputStream;
//import java.util.ArrayList;
//import java.util.Iterator;
//import java.util.List;
//import java.util.Set;
//import java.util.TreeSet;
//
//import org.springframework.stereotype.Service;
//
//import com.google.common.primitives.Bytes;
//import com.google.common.primitives.Doubles;
//import com.iqser.red.service.ocr.v1.server.model.ClippingPathStack;
//import com.iqser.red.service.ocr.v1.server.model.ElementFeatures;
//import com.pdftron.common.Matrix2D;
//import com.pdftron.common.PDFNetException;
//import com.pdftron.pdf.ColorPt;
//import com.pdftron.pdf.ColorSpace;
//import com.pdftron.pdf.Element;
//import com.pdftron.pdf.ElementBuilder;
//import com.pdftron.pdf.ElementReader;
//import com.pdftron.pdf.ElementWriter;
//import com.pdftron.pdf.GState;
//import com.pdftron.pdf.PDFDoc;
//import com.pdftron.pdf.Page;
//import com.pdftron.pdf.PageIterator;
//import com.pdftron.pdf.PathData;
//import com.pdftron.pdf.Rect;
//import com.pdftron.sdf.Obj;
//import com.pdftron.sdf.SDFDoc;
//
//import lombok.Builder;
//import lombok.SneakyThrows;
//import lombok.extern.slf4j.Slf4j;
//
//@Slf4j
//@Service
//public class InvisibleElementRemovalService {
//
// static public final double TOLERANCE = 1e-3;
//
//
// /**
// * Removes all hidden Text, Path and Image Elements from a PDF Document.
// * handled cases:
// * -Text which is transparent or is set to not render
// * -Elements outside of clipping path
// * -Elements that have been painted over by visible and filled Paths
// * unhandled cases:
// * -Elements covered by widely stroked path
// * -Elements with the same color as background
// * -Any Text set to clipping with its many interactions with other elements
// *
// * @param pdfFile The PDF file to process
// * @param delta If this flag is set only the removed Elements will be written to the output file.
// * The Elements are red if they are removed by clipping path, blue for transparency, and a green bounding box for overlap.
// * @param out OutputStream to write the resulting file to
// **/
// @SneakyThrows
// public void removeInvisibleElements(InputStream pdfFile, OutputStream out, boolean delta) {
//
// PDFDoc pdfDoc = new PDFDoc(pdfFile);
//
// ElementWriter writer = new ElementWriter();
// ElementReader reader = new ElementReader();
// Set<Long> visitedXObjIds = new TreeSet<>();
//
// for (PageIterator iterator = pdfDoc.getPageIterator(); iterator.hasNext(); ) {
//
// Page page = iterator.next();
//
// visitedXObjIds.add(page.getSDFObj().getObjNum());
//
//
// InvisibleElementRemovalContext context = InvisibleElementRemovalContext.builder()
// .reader(reader)
// .clippingPathStack(new ClippingPathStack(page.getMediaBox()))
// .delta(delta)
// .overlappedElements(new ArrayList<>())
// .visibleElements(new ArrayList<>())
// .visitedXObjIds(visitedXObjIds)
// .build();
//
// removeClippedElementsAndInvisibleTextAndRememberOverlappedElements(page, writer, context);
//
// context.visitedXObjIds().clear();
//
// removeOverlappedElements(page, writer, context);
// }
//
// try {
// pdfDoc.save(out, SDFDoc.SaveMode.LINEARIZED, null);
// } catch (Exception e) {
// log.error("File could not be saved after invisible element removal");
// throw new RuntimeException(e);
// }
//
// writer.destroy();
// reader.destroy();
// pdfDoc.close();
// }
//
//
// private void removeClippedElementsAndInvisibleTextAndRememberOverlappedElements(Page page,
// ElementWriter writer,
// InvisibleElementRemovalContext context) throws PDFNetException {
//
// context.reader().begin(page);
// writer.begin(page, ElementWriter.e_replacement, false, true, page.getResourceDict());
// processElements(writer, context);
// writer.end();
// context.reader().end();
// }
//
//
// private void processElements(ElementWriter writer, InvisibleElementRemovalContext context) throws PDFNetException {
//
// for (Element element = context.reader().next(); element != null; element = context.reader().next())
// switch (element.getType()) {
// case Element.e_image, Element.e_inline_image -> processImages(element, writer, context);
// case Element.e_text -> processText(element, writer, context);
// case Element.e_path -> processPath(element, writer, context);
// case Element.e_form -> processForm(element, writer, context);
// case Element.e_group_begin -> {
// context.clippingPathStack().enterNewGState();
// writer.writeElement(element);
// }
// case Element.e_group_end -> {
// context.clippingPathStack().leaveGState();
// writer.writeElement(element);
// }
// default -> writer.writeElement(element);
// }
// }
//
//
// private void processImages(Element imageElement, ElementWriter writer, InvisibleElementRemovalContext context) throws PDFNetException {
//
// Rect rect = imageElement.getBBox();
//
// if (rect == null) {
// return;
// }
//
// boolean inClippingPath = context.clippingPathStack().almostIntersects(rect.getX1(), rect.getY1(), rect.getWidth(), rect.getHeight());
//
// if (!context.delta() && inClippingPath) {
// context.visibleElements().add(ElementFeatures.extractFeatures(imageElement));
// }
//
// if (context.delta() ^ inClippingPath) {
// writer.writeElement(imageElement);
// }
// }
//
//
// private void processText(Element textElement, ElementWriter writer, InvisibleElementRemovalContext context) throws PDFNetException {
//
// Rect rect = textElement.getBBox();
//
// if (rect == null) {
// writer.writeElement(textElement);
// return;
// }
//
// GState gState = textElement.getGState();
//
// boolean inClippingPath = context.clippingPathStack().almostIntersects(rect.getX1(), rect.getY1(), rect.getWidth(), rect.getHeight());
//
// boolean isTextVisible = isTextRenderedVisibly(gState);
//
// if (inClippingPath && isTextVisible) {
// context.visibleElements().add(ElementFeatures.extractFeatures(textElement));
// }
// if (!context.delta()) {
// if (inClippingPath && isTextVisible) {
// writer.writeElement(textElement);
// } else if (textElement.hasTextMatrix()) {
// /*
// PDFTron Element with type "text" refers to a Tj command. If a Tm command is just above it in the pdf file, PDFTron will join the two commands and treat them as one Element.
// hasTextMatrix() checks for this case specifically. Also, Tm changes the position for a whole BT/ET segment, possibly containing multiple Tj commands.
// Therefore, the position of a following Tj is affected by not writing the first Element.
// This is why, we write only the Tm command:
// */
// writer.writeGStateChanges(textElement);
// }
// } else {
// if (!inClippingPath) {
// gState.setFillColorSpace(ColorSpace.createDeviceRGB());
// // red for elements removed by clipping path
// gState.setFillColor(new ColorPt(1, 0, 0));
// writer.writeElement(textElement);
// }
// if (!isTextVisible) {
// gState.setFillColorSpace(ColorSpace.createDeviceRGB());
// // blue for elements removed due to transparency or not rendered
// gState.setFillColor(new ColorPt(0, 0, 1));
// gState.setTextRenderMode(GState.e_fill_text);
// gState.setFillOpacity(1);
// writer.writeElement(textElement);
// }
// }
// }
//
//
// private void processForm(Element formElement, ElementWriter writer, InvisibleElementRemovalContext context) throws PDFNetException {
//
// writer.writeElement(formElement);
// Obj formObj = formElement.getXObject();
//
// if (!context.visitedXObjIds().contains(formObj.getObjNum())) {
// context.visitedXObjIds().add(formObj.getObjNum());
// // writer needs to be newly initialized when entering a new content stream
// // see ElementEditTest in PDFTron (https://www.pdftron.com/documentation/samples/android/java/ElementEditTest)
// ElementWriter formWriter = new ElementWriter();
// context.reader().formBegin();
// formWriter.begin(formObj);
//
// context.reader().clearChangeList();
// formWriter.setDefaultGState(context.reader());
//
// processElements(formWriter, context);
// formWriter.end();
// formWriter.destroy();
// context.reader().end();
// }
// }
//
//
// private void processPath(Element pathElement, ElementWriter writer, InvisibleElementRemovalContext context) throws PDFNetException {
//
// PathData pathData = pathElement.getPathData();
//
// if (pathData.getOperators().length == 0 && pathData.getPoints().length == 0) {
// writer.writeGStateChanges(pathElement);
// return;
// }
//
// GeneralPath linePath = convertToGeneralPath(pathData);
//
// //transform path to initial user space
// var ctm = pathElement.getCTM();
// var affineTransform = toAffineTransform(ctm);
// linePath.transform(affineTransform);
//
// var rect = linePath.getBounds2D();
//
// boolean inClippingPath = context.clippingPathStack().almostIntersects(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
//
// if (pathElement.isClippingPath()) {
// if (pathElement.isClipWindingFill()) {
// linePath.setWindingRule(GeneralPath.WIND_NON_ZERO);
// } else {
// linePath.setWindingRule(GeneralPath.WIND_EVEN_ODD);
// }
//
// context.clippingPathStack().intersectClippingPath(linePath);
// pathElement.setPathClip(!context.delta());
// writer.writeElement(pathElement);
//
// } else {
// if (pathElement.isWindingFill()) {
// linePath.setWindingRule(GeneralPath.WIND_NON_ZERO);
// } else {
// linePath.setWindingRule(GeneralPath.WIND_EVEN_ODD);
// }
//
// if (inClippingPath) {
// if (isFilledAndNonTransparent(pathElement)) {
// List<ElementFeatures> currentOverlappedElements = context.visibleElements()
// .stream()
// .filter(features -> almostContains(linePath, features.getBoundingBox()))
// .toList();
// context.overlappedElements().addAll(currentOverlappedElements);
// context.visibleElements().removeAll(currentOverlappedElements);
// }
// context.visibleElements().add(ElementFeatures.extractFeatures(pathElement));
// if (!context.delta()) {
// writer.writeElement(pathElement);
// }
// }
// if (context.delta() && !inClippingPath) {
// pathElement.getGState().setFillColorSpace(ColorSpace.createDeviceRGB());
// pathElement.getGState().setFillColor(new ColorPt(1, 0, 0));
// pathElement.getGState().setStrokeColorSpace(ColorSpace.createDeviceRGB());
// pathElement.getGState().setStrokeColor(new ColorPt(1, 0, 0));
// writer.writeElement(pathElement);
// }
// }
// }
//
//
// private void removeOverlappedElements(Page page, ElementWriter writer, InvisibleElementRemovalContext context) throws PDFNetException {
//
// context.reader().begin(page);
// writer.begin(page, ElementWriter.e_replacement, false, true, page.getResourceDict());
// if (context.delta()) {
// // green for element removed due to overlapping
// context.overlappedElements().forEach(feature -> drawBBox(writer, feature.getBoundingBox(), "#00FF00"));
// context.overlappedElements().clear();
// }
// processOverlappedElements(writer, context);
// writer.end();
// context.reader().end();
//
// if (context.overlappedElements().size() > 0) {
// log.warn(context.overlappedElements().size() + " overlapped elements have not been found or removed");
// }
// }
//
//
// private void processOverlappedElements(ElementWriter writer, InvisibleElementRemovalContext context) throws PDFNetException {
//
// for (Element element = context.reader().next(); element != null; element = context.reader().next()) {
// switch (element.getType()) {
// case Element.e_form -> processFormOverlappedElements(writer, element, context);
// case Element.e_path, Element.e_image, Element.e_inline_image, Element.e_text -> {
// boolean anyMatch = false;
// for (ElementFeatures elementToRemove : context.overlappedElements()) {
// if (elementToRemove.almostMatches(element)) {
// context.overlappedElements().remove(elementToRemove);
// anyMatch = true;
// break;
// }
// }
// if (!anyMatch) {
// writer.writeElement(element);
// } else if (element.getType() == 3 && element.hasTextMatrix()) {
// /*
// PDFTron Element with type "text" refers to a Tj command. If a Tm command is just above it in the pdf file, PDFTron will join the two commands and treat them as one Element.
// hasTextMatrix() checks for this case specifically. Also, Tm changes the position for a whole BT/ET segment, possibly containing multiple Tj commands.
// Therefore, the position of a following Tj is affected by not writing the first Element.
// This is why, we write only the Tm command:
// */
// writer.writeGStateChanges(element);
// }
// }
// default -> writer.writeElement(element);
// }
// }
// }
//
//
// private void processFormOverlappedElements(ElementWriter writer, Element formElement, InvisibleElementRemovalContext context) throws PDFNetException {
//
// writer.writeElement(formElement);
// Obj formObj = formElement.getXObject();
//
// if (!context.visitedXObjIds().contains(formObj.getObjNum())) {
// context.visitedXObjIds().add(formObj.getObjNum());
// // writer needs to be newly initialized when entering a new content stream
// // see ElementEditTest in PDFTron (https://www.pdftron.com/documentation/samples/android/java/ElementEditTest)
// ElementWriter formWriter = new ElementWriter();
// context.reader().formBegin();
// formWriter.begin(formObj);
//
// context.reader().clearChangeList();
// formWriter.setDefaultGState(context.reader());
//
// processOverlappedElements(formWriter, context);
// formWriter.end();
// formWriter.destroy();
// context.reader().end();
// }
// }
//
//
// private boolean isTextRenderedVisibly(GState gState) throws PDFNetException {
//
// return gState.getTextRenderMode() != GState.e_invisible_text && //
// !(gState.getTextRenderMode() == GState.e_fill_text && gState.getFillOpacity() == 0) && //
// !(gState.getTextRenderMode() == GState.e_stroke_text && gState.getStrokeOpacity() == 0) && //
// !(gState.getTextRenderMode() == GState.e_fill_stroke_text && gState.getFillOpacity() == 0 && gState.getStrokeOpacity() == 0);
// }
//
//
// private GeneralPath convertToGeneralPath(PathData pathData) throws PDFNetException {
//
// GeneralPath linePath = new GeneralPath();
// Iterator<Double> points = Doubles.asList(pathData.getPoints()).iterator();
// Iterable<Byte> operators = Bytes.asList(pathData.getOperators());
// for (var operator : operators) {
// switch (operator) {
// case PathData.e_moveto -> linePath.moveTo(points.next(), points.next());
// case PathData.e_lineto -> linePath.lineTo(points.next(), points.next());
// case PathData.e_cubicto -> linePath.curveTo(points.next(), points.next(), points.next(), points.next(), points.next(), points.next());
// case PathData.e_closepath -> linePath.closePath();
// case PathData.e_rect -> {
// double x = points.next();
// double y = points.next();
// double w = points.next();
// double h = points.next();
// linePath.moveTo(x, y);
// linePath.lineTo(x + w, y);
// linePath.lineTo(x + w, y + h);
// linePath.lineTo(x, y + h);
// linePath.closePath();
// }
// default -> throw new PDFNetException("Invalid Element Type", 0, "", "", "");
// }
// }
// return linePath;
// }
//
//
// private boolean almostContains(Shape outer, Rectangle2D inner) {
// //To address inconsistencies in the calculation of the bounding box we slightly shrink the inner rectangle
//
// double x_with_tolerance = inner.getX() >= 0 ? inner.getX() + TOLERANCE : inner.getX() - TOLERANCE;
// double y_with_tolerance = inner.getY() >= 0 ? inner.getY() + TOLERANCE : inner.getY() - TOLERANCE;
// double height_with_tolerance = inner.getHeight() - (2 * TOLERANCE);
// double width_with_tolerance = inner.getWidth() - (2 * TOLERANCE);
// Rectangle2D innerRect = new Rectangle2D.Double(x_with_tolerance, y_with_tolerance, width_with_tolerance, height_with_tolerance);
//
// return outer.contains(innerRect);
// }
//
//
// private boolean isFilledAndNonTransparent(Element element) throws PDFNetException {
//
// return element.isFilled() && element.getGState().getFillOpacity() == 1;
// }
//
//
// @SneakyThrows
// private void drawBBox(ElementWriter writer, Rectangle2D r, String hexcolor) {
//
// ColorPt colorPt = new ColorPt(Integer.valueOf(hexcolor.substring(1, 3), 16) / 255d,
// Integer.valueOf(hexcolor.substring(3, 5), 16) / 255d,
// Integer.valueOf(hexcolor.substring(5, 7), 16) / 255d);
// ElementBuilder eb = new ElementBuilder();
// Element rect = eb.createRect(r.getX(), r.getY(), r.getWidth(), r.getHeight());
// rect.setPathStroke(true);
// rect.getGState().setStrokeColorSpace(ColorSpace.createDeviceRGB());
// rect.getGState().setStrokeColor(colorPt);
// writer.writePlacedElement(rect);
//
// colorPt.destroy();
// eb.destroy();
// }
//
//
// private static AffineTransform toAffineTransform(Matrix2D ctm) throws PDFNetException {
//
// return new AffineTransform(ctm.getA(), ctm.getB(), ctm.getC(), ctm.getD(), ctm.getH(), ctm.getV());
// }
//
//
// @Builder
// private record InvisibleElementRemovalContext(
// boolean delta,
// ElementReader reader,
// ClippingPathStack clippingPathStack,
// List<ElementFeatures> overlappedElements,
// List<ElementFeatures> visibleElements,
// Set<Long> visitedXObjIds) {
//
// }
//
//}
@@ -14,6 +14,7 @@ import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iqser.red.pdftronlogic.commons.InvisibleElementRemovalService;
import com.iqser.red.service.ocr.v1.api.model.OCRStatusUpdateResponse;
import com.iqser.red.service.ocr.v1.server.configuration.MessagingConfiguration;
import com.iqser.red.service.ocr.v1.server.settings.OcrServiceSettings;
@@ -69,10 +70,10 @@ public class OCRService {
try (ByteArrayOutputStream transferOutputStream = new ByteArrayOutputStream()) {
try (InputStream fileStream = fileStorageService.getOriginalFileAsStream(dossierId, fileId)) {
long removalStart = System.currentTimeMillis();
log.debug("Start invisible element removal for file with dossierId {} and fileId {}", dossierId, fileId);
log.info("Start invisible element removal for file with dossierId {} and fileId {}", dossierId, fileId);
invisibleElementRemovalService.removeInvisibleElements(fileStream, transferOutputStream, false);
long removalEnd = System.currentTimeMillis();
log.debug("Invisible element removal successful for file with dossierId {} and fileId {}, took {}s",
log.info("Invisible element removal successful for file with dossierId {} and fileId {}, took {}s",
dossierId,
fileId,
format("%.1f", (removalEnd - removalStart) / 1000.0));
@@ -40,10 +40,6 @@ management:
storage:
signer-type: 'AWSS3V4SignerType'
bucket-name: 'redaction'
region: 'us-east-1'
endpoint: 'https://s3.amazonaws.com'
backend: 's3'
pdftron.license: ${PDFTRON_LICENSE}
@@ -16,15 +16,18 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.io.ClassPathResource;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iqser.red.service.ocr.v1.server.multitenancy.TenantContext;
import com.iqser.red.service.ocr.v1.server.service.FileStorageService;
import com.iqser.red.service.ocr.v1.server.service.OCRService;
import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileType;
import com.pdftron.pdf.OCRModule;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import io.micrometer.prometheus.PrometheusTimer;
import lombok.SneakyThrows;
@Disabled // OCRModule is not available on build server. If you want to run the test set the property at the top.
@SpringBootTest(properties = {"pdftron.ocrmodule.path=/YourOCRModulePath"})
public class OcrServiceIntegrationTest extends AbstractTest {
@@ -39,13 +42,12 @@ public class OcrServiceIntegrationTest extends AbstractTest {
@BeforeEach
@SneakyThrows
@Disabled
public void assertOCRModuleIsLoaded() {
assert OCRModule.isModuleAvailable();
}
@Test
@Disabled // OCRModule is not available on build server. If you want to run the test set the property at the top.
public void testOCRMetrics() {
testOCR("Watermark");
@@ -61,7 +63,6 @@ public class OcrServiceIntegrationTest extends AbstractTest {
@Test
@Disabled // OCRModule is not available on build server. If you want to run the test set the property at the top.
public void testOcr() {
// check visually for most of the images containing text, the resulting text is kind of nonsense, just ensure it is there
String text = testOCR("StitchedImagesMultiPage");
@@ -69,7 +70,6 @@ public class OcrServiceIntegrationTest extends AbstractTest {
@Test
@Disabled // OCRModule is not available on build server. If you want to run the test set the property at the top.
public void testManyRotatedImages() {
// check visually for most of the images containing text, the resulting text is kind of nonsense, just ensure it is there
String text = testOCR("manyRotatedImages");
@@ -78,7 +78,6 @@ public class OcrServiceIntegrationTest extends AbstractTest {
@Test
@Disabled // OCRModule is not available on build server. If you want to run the test set the property at the top.
public void testMergeImages() {
// check visually for most of the images containing text, the resulting text is kind of nonsense, just ensure it is there
String text = testOCR("merge_images");
@@ -99,7 +98,6 @@ public class OcrServiceIntegrationTest extends AbstractTest {
@Test
@Disabled // OCRModule is not available on build server. If you want to run the test set the property at the top.
public void testOCRWatermark() {
assertThat(testOCR("Watermark")).contains("syngenta");
@@ -107,7 +105,6 @@ public class OcrServiceIntegrationTest extends AbstractTest {
@Test
@Disabled // OCRModule is not available on build server. If you want to run the test set the property at the top.
public void testOCRInvisibleText() {
String text = testOCR("InvisibleText");
@@ -122,7 +119,7 @@ public class OcrServiceIntegrationTest extends AbstractTest {
ClassPathResource pdfFileResource = new ClassPathResource("files/" + fileName + ".pdf");
var originId = FileStorageService.getStorageId("dossier", "file", FileType.ORIGIN);
try (var fileStream = pdfFileResource.getInputStream()) {
storageService.storeObject(originId, fileStream);
storageService.storeObject(TenantContext.getTenantId(), originId, fileStream);
}
try (var out = new FileOutputStream(getTemporaryDirectory() + "/" + fileName + ".pdf")) {
ocrService.runOcrOnDocument("dossier", "file", out);
@@ -9,16 +9,18 @@ import java.io.FileOutputStream;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import com.iqser.red.pdftronlogic.commons.InvisibleElementRemovalService;
import com.iqser.red.service.ocr.v1.server.AbstractTest;
import lombok.SneakyThrows;
public class InvisibleElementRemovalServiceTest extends AbstractTest {
@Autowired
private InvisibleElementRemovalService invisibleElementRemovalService;
@Autowired
private InvisibleElementRemovalService invisibleElementRemovalService;
@Test
@@ -32,7 +32,7 @@ public class FileSystemBackedStorageService implements StorageService {
@SneakyThrows
@Override
public InputStreamResource getObject(String objectId) {
public InputStreamResource getObject(String tenantId, String objectId) {
var res = dataMap.get(objectId);
if (res == null) {
@@ -44,28 +44,22 @@ public class FileSystemBackedStorageService implements StorageService {
@Override
public void deleteObject(String objectId) {
public void deleteObject(String tenantId, String objectId) {
dataMap.remove(objectId);
}
@Override
public boolean objectExists(String objectId) {
public boolean objectExists(String tenantId, String objectId) {
return dataMap.containsKey(objectId);
}
@Override
public void init() {
}
@Override
@SneakyThrows
public <T> void storeJSONObject(String objectId, T any) {
public <T> void storeJSONObject(String tenantId, String objectId, T any) {
File tempFile = File.createTempFile("test", ".tmp");
getMapper().writeValue(new FileOutputStream(tempFile), any);
@@ -81,7 +75,7 @@ public class FileSystemBackedStorageService implements StorageService {
@Override
@SneakyThrows
public <T> T readJSONObject(String objectId, Class<T> clazz) {
public <T> T readJSONObject(String tenantId, String objectId, Class<T> clazz) {
if (dataMap.get(objectId) == null || !dataMap.get(objectId).exists()) {
throw new StorageObjectDoesNotExist("Stored object not found");
@@ -104,7 +98,7 @@ public class FileSystemBackedStorageService implements StorageService {
@Override
@SneakyThrows
public void storeObject(String objectId, InputStream stream) {
public void storeObject(String tenantId, String objectId, InputStream stream) {
File tempFile = File.createTempFile("test", ".tmp");
+2 -2
View File
@@ -13,7 +13,7 @@
<groupId>com.iqser.red.service</groupId>
<artifactId>ocr-service-v1</artifactId>
<version>1.0-SNAPSHOT</version>
<version>3.0-SNAPSHOT</version>
<packaging>pom</packaging>
@@ -27,7 +27,7 @@
<dependency>
<groupId>com.iqser.red</groupId>
<artifactId>platform-commons-dependency</artifactId>
<version>1.21.0</version>
<version>1.22.0</version>
<scope>import</scope>
<type>pom</type>
</dependency>
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.iqser.red.service</groupId>
<artifactId>ocr-service</artifactId>
<version>1.0-SNAPSHOT</version>
<version>3.0-SNAPSHOT</version>
<packaging>pom</packaging>