I'm writing a Spring Boot Application that uses Bytedeco's Java Wrapper for Tesseract OCR to parse Japanese text. I've managed to get Tesseract working fine when running outside of Spring Boot, but when I use it from within the Spring Boot application it gives me nonsense results.
For example, given the following image:
If I run the following function, the result is reasonable:
fun main() {
val api = tesseract.TessBaseAPI()
api.Init("src/main/resources/tessdata", "jpn_vert")
api.SetPageSegMode(tesseract.PSM_SINGLE_BLOCK_VERT_TEXT)
val pixImage = lept.pixRead("src/main/resources/image.png")
api.SetImage(pixImage)
val result = api.GetUTF8Text()
System.out.println("Parsed text: " + result?.string)
}
Prints:
Parsed text:
坊っちゃん
夏目 滞 石
If I run it from within a Spring Boot Web Socket, however, the result is not:
#SpringBootApplication
open class BootApplication
fun main(args: Array<String>) {
runApplication<BootApplication>(*args)
}
#Configuration
#EnableWebSocket
open class WebSocketConfiguration: WebSocketConfigurer {
#Bean
open fun createWebSocketContainer(): ServletServerContainerFactoryBean {
val container = ServletServerContainerFactoryBean()
container.maxBinaryMessageBufferSize = 1024000
return container
}
override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {
registry.addHandler(Endpoint(), "/parse").setAllowedOrigins("*")
}
}
class Endpoint: AbstractWebSocketHandler() {
#Throws(IOException::class)
override fun handleBinaryMessage(session: WebSocketSession?, message: BinaryMessage?) {
// Same code as above:
val api = tesseract.TessBaseAPI()
api.Init("src/main/resources/tessdata", "jpn_vert")
api.SetPageSegMode(tesseract.PSM_SINGLE_BLOCK_VERT_TEXT)
val pixImage = lept.pixRead("src/main/resources/image.png")
api.SetImage(pixImage)
val result = api.GetUTF8Text()
System.out.println("Parsed text:\n" + result?.string)
}
}
Prints the following when handleBinaryMessage is called:
Parsed text:
蝮翫▲縺。繧?繧?
螟冗岼 貊? 遏ウ
I ran a quick test on some English text and that worked fine, so I assume this issue is language-specific.
I'm running the Boot application with the bootRun task from the Spring Boot Gradle plugin which starts an Apache Tomcat service. My first thought is that this has something to do with the fact that the Tesseract wrapper is a JNI library and the environment it's running in (Tomcat) isn't the same. If that's the case, is there some extra configuration that needs to be done to get Tesseract to work with Spring Boot and Tomcat?
For reference, my build.gradle is as follows:
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.10'
id("org.springframework.boot") version "2.1.0.RELEASE"
}
repositories {
mavenCentral()
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation group: "org.bytedeco.javacpp-presets", name: "tesseract", version: "4.0.0-rc2-1.4.3"
implementation group: "org.bytedeco.javacpp-presets", name: "tesseract", version: "4.0.0-rc2-1.4.3", classifier: "windows-x86_64"
implementation group: "org.bytedeco.javacpp-presets", name: "leptonica", version: "1.76.0-1.4.3", classifier: "windows-x86_64"
implementation group: "org.springframework.boot", name: "spring-boot", version: "2.1.0.RELEASE"
implementation group: "org.springframework.boot", name: "spring-boot-starter-web", version: "2.1.0.RELEASE"
implementation group: "org.springframework", name: "spring-websocket", version: "5.1.2.RELEASE"
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
Edit
Looks like it was an encoding issue. Java's file.encoding system property was set to UTF-8 when running from outside of Boot, but set to windows-31j when in Boot. Switching result?.string to result?.getString("UTF-8") fixed it.
I'm going to chalk this up to a bug with Bytedeco's Tesseract wrapper. I did the equivalent test with tess4j and had no problems:
val imageFile = File("src/main/resources/image.png")
val tess = Tesseract()
tess.setPageSegMode(PSM_SINGLE_BLOCK_VERT_TEXT)
tess.setLanguage("jpn_vert")
System.out.println("Parsed text:\n" + tess.doOCR(imageFile))
Gives me:
坊っちゃん
夏目 滞 石
as expected.
I've filed a ticket on their github, so hopefully this will be cleared up before long.
Related
I am getting Whitelabel Error Page when using the SpringBoot + Kotlin + SpringDoc + WebFlux. No documentation is generated despite dependencies from offical springdoc documentation page
enter image description here.
application.yaml does not contain any properties specified
private object Version {
const val kotlinCoroutinesVersion = "1.6.4"
const val openApiVersion = "1.6.12"
}
plugins {
val kotlinVersion = "1.8.10"
id("org.springframework.boot") version "3.0.2"
id("io.spring.dependency-management") version "1.1.0"
id("org.jetbrains.kotlin.plugin.spring") version kotlinVersion
}
dependencies {
// Coroutines dependencies
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Version.kotlinCoroutinesVersion}")
runtimeOnly("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:${Version.kotlinCoroutinesVersion}")
// Spring dependencies
implementation("org.springframework.boot:spring-boot-starter-webflux")
// Swagger
implementation("org.springdoc:springdoc-openapi-webflux-ui:${Version.openApiVersion}")
implementation("org.springdoc:springdoc-openapi-kotlin:${Version.openApiVersion}")
// Validation
implementation("javax.validation:validation-api:2.0.1.Final")
}
Example Controller looks like:
#RestController
#RequestMapping("/product")
internal class OrderController(
private val orderCommand: OrderCommand
) {
#PostMapping
suspend fun saveProduct(#Valid #RequestBody createOrderRequest: CreateOrderRequest): ResponseEntity<OrderCreatedResponse> {
// Some code
}
Main function:
#EnableWebFlux
#SpringBootApplication
class Application
fun main(args: Array<String>) {
SpringApplication.run(Application::class.java, *args)
}
Any ideas what can I missed? I checked locally on localhost:8080/v3/api-docs.yaml and localhost:8080/swagger-ui.html but still no page has been generated
Adding
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.boot:spring-boot-starter-graphql")
to dependencies solved the issue
Our project needs to serve scaled images. AppEngine ImageApi has a wonderful method called getServingUrl. But this API is only accessible from AppEngine.
Problem
I've created a PoC project with SpringBoot and gradle for AppEngine. And whenever I call a simple endpoint that should call getServingUrl I get the following error:
Can’t make API call blobstore.CreateEncodedGoogleStorageKey in a thread that is neither the original request thread nor a thread created by ThreadManage
But when I do a similar setup that is based on Servlets, it works as expected.
SpringBoot Setup
Here is an example of some files from my setup.
build.gradle:
;; https://github.com/GoogleCloudPlatform/app-gradle-plugin
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.google.cloud.tools:appengine-gradle-plugin:2.2.0'
}
}
plugins {
id 'org.springframework.boot' version '2.2.6.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
apply plugin: 'com.google.cloud.tools.appengine' // App Engine tasks
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation "com.google.appengine:appengine-api-1.0-sdk:2.0.4"
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
test {
useJUnitPlatform()
}
appengine { // App Engine tasks configuration
deploy { // deploy configuration
projectId = 'GCLOUD_CONFIG' // delegate to project in gcloud config
version = 'GCLOUD_CONFIG' // delegate to gcloud to generate a version
}
}
appengine/app.yml
runtime: java11
Controller
package com.example.demo.controller;
import com.google.appengine.api.images.ImagesServiceFactory;
import com.google.appengine.api.images.ServingUrlOptions;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class ImageController {
#RequestMapping("/image-url")
public ImageUrlDto hello(#RequestParam String bucket, #RequestParam String image) {
ServingUrlOptions options = ServingUrlOptions.Builder
.withGoogleStorageFileName(String.format("/gs/%s/%s", bucket, image));
return new ImageUrlDto(ImagesServiceFactory.getImagesService().getServingUrl(options));
}
}
class ImageUrlDto {
String url;
public ImageUrlDto(String url) {
this.url = url;
}
}
Question
Servlet setup is working correctly, but spring setup no. I've read that Image API is accessible only within AppEngine standart environemnt and request thread/ThreadManager spawned thread.
Am I missing something for that Spring setup? Probably that requires some another form of deployment or thread configuration in order to work with Image API?
I am getting the following issue in Jenkins when updating gradle;
Execution failed for task ':xxx:extractModuleInfo'.
06:38:45 > Artifact xxxxx.jar wasn't produced by this build.
I have been researching on the issue and I think it's known issue when updating gradle.
Please refer to the section Publishing Spring Boot Applications in this link:
https://docs.gradle.org/current/userguide/upgrading_version_6.html
Here is the buildgradle of the application:
import java.time.format.DateTimeFormatter
import java.time.ZonedDateTime
apply plugin: 'org.springframework.boot'
apply plugin: 'war'
def checkpoint = version
description 'Spring Boot / MVC web application (controllers for SEAR services, etc)'
springBoot {
// Generates build info and will be used in /info endpoint
buildInfo()
}
/*
bootRun {
addResources = false
systemProperties = System.properties
main = 'creditcard.lifecycle.CreditCardApplication'
jvmArgs = [ "-Djavax.net.ssl.trustStore=..//cacerts"]
}
*/
dependencies {
implementation project(':xx')
implementation project(':xx')
implementation project(':xxxx')
implementation project(':xxxxxxxxxxxxxxxxxxxxxx')
implementation project(':xxxxxxxxxxxxxxxxxxxxxxx')
implementation project(':xxxxxxxxxxxxxxx')
implementation project(':xxxxxxxxxxxxxxxx')
implementation project(':xxx')
implementation project(':xxxxx)
implementation project(':xxxxx')
implementation project(':xxxxx')
implementation project(':xxxxx')
implementation project(':xxxxx')
implementation libraries.wf_retrofit
implementation libraries.commons_lang3
implementation ('org.springframework.boot:spring-boot-starter-web') {
exclude module: "spring-boot-starter-tomcat"
exclude module: "tomcat-embed-core"
exclude module: "tomcat-embed-el"
exclude module: "tomcat-embed-websocket"
exclude module: "tomcat-annotations-api"
}
implementation ('org.springframework.boot:spring-boot-starter-tomcat:1.5.8.RELEASE')
implementation ('wf.authx:authx:0.9+')
implementation libraries.jackson
implementation libraries.searj
implementation libraries.commonscodec
implementation ('org.springframework.boot:spring-boot-starter-actuator')
//include schema after it is built
//compile group: 'wf.ebs', name: 'schemas', version: '2018.1-SNAPSHOT'
runtimeOnly 'org.springframework.boot:spring-boot-starter-undertow'
}
configurations {
compile.exclude module: 'spring-boot-starter-tomcat'
compile.exclude group: 'org.apache.tomcat'
compile.exclude module: "tomcat-embed-el"
compile.exclude module: "searj-authx-spring-boot-starter"
}
task createCheckpointFile {
doLast {
//Need to pass variables as jenkins parameters
//def checkpoint = "xxxx_${Release}.${BUILD_NUMBER}"
println "Checkpoint = ${checkpoint}"
def TODAY_DT_US = ZonedDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z"))
String fileContent = "BUILD_CHECKPOINT=${checkpoint}\nBUILD TSTAMP: ${TODAY_DT_US} \nBUILD_DIR=$projectDir"
new File("${buildDir}/xxxxx.checkpoint").write(fileContent)
}
}
/*
jar {
from file("${buildDir}/xxxx.checkpoint")
baseName = 'xxxxxx'
manifest {
attributes("Implementation-Title": "xxxx",
"Implementation-Version": "$project.version")
}
}
*/
//bootRepackage.enabled = false
//jar.dependsOn createCheckpointFile
war {
enabled = true
archiveFileName = 'xxxx.war'
//copy-move
webInf { from("${buildDir}/xxxx.checkpoint") }
}
war.dependsOn createCheckpointFile
How is that components feature adding that check to the jar?
Add/modify jar task as below in build.gradle
jar {
enabled = true
}
This should fix the issue
I'm trying to migrate a Spring Boot project from Kotlin 1.2.71 to 1.3.0.
When I update the Kotlin version, the application context fails to load with the following stack trace:
[...]
Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'universityController' method
public final com.vindedu.api.view.university.UniversityApplicationOverview com.vindedu.api.controller.UniversityController.getApplications(org.springframework.security.core.userdetails.UserDetails,boolean)
to {[/uni/applications],methods=[GET]}: There is already 'universityController' bean method
public static com.vindedu.api.view.university.UniversityApplicationOverview com.vindedu.api.controller.UniversityController.getApplications$default(com.vindedu.api.controller.UniversityController,org.springframework.security.core.userdetails.UserDetails,boolean,int,java.lang.Object) mapped.
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry.assertUniqueMethodMapping(AbstractHandlerMethodMapping.java:576)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry.register(AbstractHandlerMethodMapping.java:540)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.registerHandlerMethod(AbstractHandlerMethodMapping.java:264)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.detectHandlerMethods(AbstractHandlerMethodMapping.java:250)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.initHandlerMethods(AbstractHandlerMethodMapping.java:214)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.afterPropertiesSet(AbstractHandlerMethodMapping.java:184)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.afterPropertiesSet(RequestMappingHandlerMapping.java:127)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1687)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1624)
... 98 more
The following functions in my UniversityController class map to /uni/applications/...:
#ApiOperation("Get applications received from students", tags = ["University API"],
authorizations = [Authorization(value = "basicAuth")])
#RequestMapping(method = [RequestMethod.GET], path = ["/uni/applications"])
fun getApplications(#AuthenticationPrincipal user: UserDetails, #RequestParam("visible") visible: Boolean = true): UniversityApplicationOverview {
return universityMessagingService.getApplications(user, visible)
}
#ApiOperation("Toggle application visibility for university", tags = ["University API"],
authorizations = [Authorization(value = "basicAuth")])
#RequestMapping(method = [RequestMethod.PUT], path = ["/uni/applications/{id}/visible"])
fun toggleApplicationVisibility(#AuthenticationPrincipal user: UserDetails,
#PathVariable id: Long): UniversityApplicationDetails {
return universityMessagingService.toggleApplicationVisibility(user, id)
}
#ApiOperation("Get application received from student", tags = ["University API"],
authorizations = [Authorization(value = "basicAuth")])
#RequestMapping(method = [RequestMethod.GET], path = ["/uni/applications/{id}"])
fun getApplication(#AuthenticationPrincipal user: UserDetails,
#PathVariable id: Long): UniversityApplicationDetails {
return universityMessagingService.getApplication(user, id)
}
#ApiOperation("Update application status", tags = ["University API"],
authorizations = [Authorization(value = "basicAuth")])
#RequestMapping(method = [RequestMethod.PUT], path = ["/uni/applications/{id}/status"])
fun updateApplicationStatus(#AuthenticationPrincipal user: UserDetails,
#PathVariable id: Long,
#Valid #RequestBody updatedStatus: ApplicationStatusUpdate): UniversityApplicationDetails {
return universityMessagingService.updateApplicationStatus(user, id, updatedStatus)
}
When I deleted my mappings listed above, I saw the same stack trace at runtime for another controller.
This project runs flawless with Kotlin 1.2.71. I appreciate any suggestions very much to make it work with Kotlin 1.3.0! My full build.gradle is listed below.
Please note:
I compile and run this project with the following JDK:
java version "11" 2018-09-25 Java(TM) SE Runtime Environment 18.9
(build 11+28) Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11+28,
mixed mode)
I can't update Spring Boot yet, due to classes in the project that depend on Spring Boot 1.x.
build.gradle:
buildscript {
ext.kotlin_version = '1.3.0' // Was '1.2.71'
ext.spring_boot_version = '1.5.4.RELEASE'
repositories {
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // Required for Kotlin integration
classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
classpath "org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_version"
}
}
apply plugin: 'idea'
apply plugin: 'kotlin'
apply plugin: 'kotlin-allopen'
apply plugin: 'kotlin-noarg'
apply plugin: 'org.springframework.boot'
apply plugin: 'application'
allOpen {
annotation("org.springframework.boot.autoconfigure.SpringBootApplication")
annotation("org.springframework.stereotype.Service")
annotation("org.springframework.context.annotation.Configuration")
}
noArg {
annotation("javax.persistence.Entity")
}
jar {
baseName = 'vindedu-api'
version = '0.0.1'
}
repositories {
jcenter()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" // Required for Kotlin integration
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" // Required for Kotlin integration
compile 'com.fasterxml.jackson.module:jackson-module-kotlin:2.9.2'
compile 'org.springframework.boot:spring-boot-starter-security'
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'org.springframework.boot:spring-boot-starter-data-jpa'
compile 'org.springframework.boot:spring-boot-starter-actuator'
compile 'org.springframework.session:spring-session:1.3.1.RELEASE'
compile 'io.springfox:springfox-swagger2:2.7.0'
compile 'io.springfox:springfox-swagger-ui:2.7.0'
compile 'org.postgresql:postgresql:42.1.4'
compile 'org.flywaydb:flyway-core:4.2.0'
// JAXB dependencies don't ship with the JDK anymore
compile 'javax.xml.bind:jaxb-api:2.3.1'
compile 'com.sun.xml.bind:jaxb-impl:2.3.1'
compile 'javax.activation:activation:1.1.1'
testCompile 'org.springframework.boot:spring-boot-starter-test'
testCompile 'com.h2database:h2:1.4.196'
}
task wrapper(type: Wrapper) {
gradleVersion = '4.10.2'
}
springBoot {
mainClass = 'com.vindedu.api.ApplicationKt'
}
bootRun {
systemProperty("PROP_NAME", "prop-value")
}
test {
maxParallelForks = Runtime.runtime.availableProcessors() / 3
}
From 1.3.0 Kotlin compiler doesn't generate bridge flag for default methods and new bytecode are only supported in newer Spring Boot versions. Please upgrade to Spring Boot 2. Reference issue: https://youtrack.jetbrains.com/issue/KT-27947
I'm trying to build a simple application, using gradle, groovy, and spring-boot framework with this code:
#Grab("thymeleaf-spring4")
#Controller
class ViewBasedApp {
def chapters = ["Quick Start With Groovy",
"Quick Start With Java",
"Debugging and Managing Your App",
"Data Access with Spring Boot",
"Securing Your App"]
#RequestMapping("/")
def home(#RequestParam(value="name", defaultValue="World") String n) {
new ModelAndView("home")
.addObject("name", n)
.addObject("chapters", chapters)
}
}
This is my build.gradle file:
group 'LoginApp'
version '1.0-SNAPSHOT'
apply plugin: 'groovy'
repositories {
mavenCentral()
}
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.3.11'
testCompile group: 'junit', name: 'junit', version: '4.12'
compile("org.springframework.boot:spring-boot-starter-web")
//compile("org.thymeleaf:thymeleaf-spring4") //first try
// compile("nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect") //first try
compile 'org.springframework:spring-webmvc:3.0.0.RELEASE'
// https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf-spring4 //secound try
compile group: 'org.thymeleaf', name: 'thymeleaf-spring4', version: '3.0.0.RELEASE'
// compile group: 'org.thymeleaf', name: 'thymeleaf-spring4', version: '4.1.6.RELEASE' //third try
}
html file:
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Learning Spring Boot - Chapter 1</title>
</head>
<body>
<p th:text="'Hello, ' + ${name}"></p>
<ol>
<li th:each="chapter : ${chapters}" th:text="${chapter}"></li>
</ol>
</body>
</html>
To launch my app I using spring boot CLI. When I have been using "spring run" in command prompt, I always have got the same error:
Could not find artifact :thymeleaf-spring4:jar: in local (file:/C:/Users/kubas/repository).
I have tried to add "thymeleaf-spring4.jar" - downloaded from maven page, to folder "repository" and nothing, always the same error.
Can anybody suggest on this?
I resolved issue by adding dependency from local Repository:
buildscript {
repositories {
mavenLocal()
}
dependencies {
classpath("org.thymeleaf:thymeleaf-spring5:3.0.9.RELEASE")
}
}
repositories {
mavenCentral()
}
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.3.11'
testCompile group: 'junit', name: 'junit', version: '4.12'
compile("org.springframework.boot:spring-boot-starter-web")
compile("nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect") //first try
compile 'org.springframework:spring-webmvc:4.0.6.RELEASE'
compile 'org.springframework:spring-web:4.0.6.RELEASE' //#EnableWebMvc #ComponentScan
}
Now thymeleaf is taken from C:\Users\kubas\org\thymeleaf\thymeleaf-spring5
I had to change annotation in app.groovy to #Grab("thymeleaf-spring5")
but now I can't configure path to my templates file. My path to this file is:
F:\gradle_login_aPP\src\main\resources\templates ...
This is the error:
Cannot find template location: classpath:/templates/
I have tried add application.properties file in \src\main\resources\ but it doesn't work. I know it is possible to add default prefix, but I can't find information about file, where i need to add this. Any Suggestion, how to run this templates with gradle project?
I resolved this issue. I have used .html file and this is the way how I use this file in my Controller Class:
#RestController
class LoginServiceApplication {
#RequestMapping("/")
String home() {
def htmlContent = new File("F:\\APKA_W_SPRINGU\\Spring-apps-master\\AppWithLoginPage\\LoginService\\src\\main\\resources\\templates\\index.html").text
htmlContent
}
}
For the first question - all I had have to do was a run main class in intelijj using button run (Class must not contain #Grab annotation!), before I resolved this problem I was using spring CLI and that was a problem. Now everything works fine.