GraalVM Quarkus Locale in native mode - quarkus
I have an unexpected behavior with available locales when native build. I have only one locale available in native mode.
My application is very simple :
#ApplicationPath("/api")
public class ApplicationPathConfiguration extends Application {
}
#Path("/locales")
public class LocaleController {
#GET
#Produces(MediaType.APPLICATION_JSON)
public Locale[] get() {
return Locale.getAvailableLocales();
}
}
After checkout if I launch the application in dev mode :
mvn quarkus:dev
You can call the endpoint : http://localhost:8080/api/locales
curl http://localhost:8080/api/locales
This endpoint return a lot of locales :
["","nn","ar_JO","bg","kea","nds","zu","am_ET","fr_DZ","ti_ET","bo_CN","hsb","qu_EC","ta_SG","lv","en_NU","zh_SG_#Hans","en_MS","en_GG","en_JM","vo","kkj","sr_ME","sv_SE","es_BO","dz_BT","mer","sah","en_ZM","fr_ML","br","ha_NG","ar_SA","fa_AF","dsb_DE","sk","os_GE","ml","en_MT","en_LR","ar_TD","en_GH","en_IL","sv","cs","el","tzm_MA","af","sw_UG","ses_ML","smn","tk_TM","sr_ME_#Cyrl","ar_EG","dsb","lkt_US","vai_LR_#Latn","ji_001","yo_NG","se_NO","khq","sw_CD","vo_001","en_PW","pl_PL","fil_PH","it_VA","sr_CS","ne_IN","es_PH","es_ES","es_CO","bg_BG","ji","ar_EH","bs_BA_#Latn","en_VC","nds_DE","nb_SJ","es_US","agq","hsb_DE","en_US_POSIX","en_150","ar_SD","en_KN","ha_NE","pt_MO","ebu","ro_RO","zh__#Hans","lb_LU","sr_ME_#Latn","es_GT","so_KE","dje_NE","bas_CM","fr_PM","ar_KM","fr_MG","no_NO_NY","es_CL","mn","agq_CM","kam_KE","teo","tr_TR","eu","fa_IR","en_MO","wo","shi__#Tfng","en_BZ","sq_AL","ar_MR","es_DO","ru","twq_NE","az","nmg_CM","fa","kl_GL","en_NR","nd","kk","az__#Cyrl","en_MP","en_GD","tk","hy","shi__#Latn","en_BW","en_AU","en_CY","kab_DZ","kde_TZ","ta_MY","ti_ER","nus_SS","en_RW","nd_ZW","sv_FI","ksb","luo","lb","ne","en_IE","zh_SG","ln_CD","en_KI","nnh_CM","om_ET","no","ja_JP","my","ka","ar_IL","mgh","or_IN","fr_MF","shi","kl","en_SZ","rwk_TZ","zh","es_PE","mgh_MZ","saq","az__#Latn","ta","en_GB","lag","zh_HK_#Hant","ar_SY","ksf_CM","bo","kk_KZ","es_PA","tt_RU","om_KE","ar_PS","en_AS","fr_VU","zh_TW","bez","kln","fr_MC","kw","pt_MZ","fr_NE","vai__#Latn","ksb_TZ","ksh","ur_IN","ln","en_JE","gsw_CH","ln_CF","en_CX","luy_KE","pt","en_AT","gl","kkj_CM","sr__#Cyrl","yue_CN_#Hans","es_GQ","kn_IN","ar_YE","to","en_SX","ga","qu","ru_KZ","en_TZ","et","en_PR","mua","ko_KP","in","ps","sn","nl_SR","rof","en_BS","km","zgh","fr_NC","be","gv","es","dua","gd_GB","jgo","nl_BQ","fr_CM","gsw","uz_UZ_#Cyrl","pa_IN_#Guru","en_KE","guz","mfe","asa_TZ","teo_UG","ja","fr_SN","or","brx","fr_MA","pt_LU","fr_BL","en_NL","mgo_CM","ln_CG","te","sl","ko_KR","el_CY","mr_IN","ha","es_MX","lrc_IR","gsw_FR","es_HN","hu_HU","ff_SN","sbp","sq_MK","sr_BA_#Cyrl","fi","uz","bs__#Cyrl","et_EE","sr__#Latn","en_SS","sw","bo_IN","fy_NL","ar_OM","tr_CY","nmg","rm","en_MG","fr_BI","uz_UZ_#Latn","bn","dua_CM","de_IT","lrc_IQ","vai__#Vaii","kn","fr_TN","sr_RS","de_CH","bn_BD","nnh","fr_PF","en_ZA","gu","pt_GQ","vun_TZ","jmc_TZ","en_TV","lo","fr_FR","en_PN","en_MH","fr_BJ","zh__#Hant","cu_RU","zh_HK_#Hans","nl_NL","sah_RU","en_GY","ps_AF","bs__#Latn","ky","mas","dyo_SN","os","bs_BA_#Cyrl","nl_CW","ar_DZ","sk_SK","pt_CH","fr_GQ","ff_CM","am","en_NG","fr_CI","ki_KE","en_PK","zh_CN","en_LC","rw","brx_IN","wo_SN","iw","gv_IM","mk_MK","en_TT","dav","sl_SI","fr_HT","te_IN","nl_SX","lrc","ses","ce","fr_CG","fr_BE","jgo_CM","mt_MT","es_VE","mg","mr","mer_KE","ko","nds_NL","en_BM","nb_NO","ak","seh","kde","dz","kea_CV","mgo","vi_VN","en_VU","en_US","to_TO","mfe_MU","seh_MZ","fr_BF","pa__#Guru","it_SM","fr_YT","gu_IN","ii_CN","pa_PK_#Arab","ast","fr_RE","fi_FI","yue__#Hans","ca_FR","sr_BA_#Latn","bn_IN","fr_GP","pa","zgh_MA","uk_UA","fr_DJ","rn","tg","rwk","hu","fr_CH","en_NF","twq","ha_GH","sr_XK_#Cyrl","bm","ar_SS","en_GU","nl_AW","de_BE","en_AI","en_CM","xog_UG","cs_CZ","tr","ca_ES","cgg","rm_CH","nyn_UG","ru_MD","ms_MY","ta_LK","ksf","en_TO","cy","en_PG","fr_CF","pt_TL","sq","fr","tg_TJ","en_ER","qu_PE","sr_BA","es_PY","de","es_EC","kok_IN","lg_UG","zu_ZA","fr_TG","sr_XK_#Latn","en_PH","ig_NG","fr_GN","prg_001","cgg_UG","zh_MO_#Hans","ksh_DE","lg","ru_RU","se_FI","ff","en_DM","en_CK","sd","ar_MA","ga_IE","en_BI","en_AG","fr_TD","en_WS","fr_LU","ebu_KE","bem_ZM","xog","ewo_CM","fr_CD","so","rn_BI","en_NA","ar_ER","kab","ms","nus","sn_ZW","prg","iw_IL","ug","es_EA","th_TH_TH_#u-nu-thai","hi","fr_SC","ca_IT","lag_TZ","en_SL","teo_KE","no_NO","ca_AD","zh_MO_#Hant","en_SH","vai","qu_BO","haw_US","vi","fr_CA","de_LU","sq_XK","dyo","en_KY","mt","it_CH","de_DE","si_LK","luo_KE","en_DK","yav","so_DJ","lt_LT","it_IT","eo","kam","ar_SO","en_ZW","ro","eo_001","ee","en_UM","nn_NO","fr_MU","pl","se_SE","en_TK","en_SI","mua_CM","ur","uz__#Arab","vai_LR_#Vaii","saq_KE","se","pt_GW","lo_LA","chr","ar_LB","af_ZA","ms_SG","ee_TG","ln_AO","be_BY","ff_GN","yue__#Hant","in_ID","es_BZ","ar_AE","hr_HR","luy","as","rof_TZ","it","pt_CV","ks_IN","uk","my_MM","ur_PK","mn_MN","da_DK","en_FM","es_PR","wae_CH","mzn","en_BE","ii","tt","fr_WF","ru_BY","mzn_IR","naq","fo_DK","en_SG","ee_GH","ar_BH","kln_KE","tzm","fur","om","hi_IN","en_CH","asa","yo_BJ","fo_FO","ast_ES","fr_KM","bez_TZ","fr_MQ","en_SD","es_AR","en_MY","ja_JP_JP_#u-ca-japanese","es_SV","pt_BR","ml_IN","sbp_TZ","fil","en_FK","uz__#Cyrl","is_IS","yue_HK_#Hant","hy_AM","en_GM","en_DG","fo","ne_NP","hr","pt_ST","ak_GH","lt","uz_AF_#Arab","fur_IT","ta_IN","ccp","en_SE","fr_GF","lkt","zh_CN_#Hans","is","es_419","si","pt_AO","en_001","en","guz_KE","gsw_LI","ccp_BD","es_IC","ca","ru_KG","fr_MR","ar_TN","ks","zh_TW_#Hant","bm_ML","kw_GB","ug_CN","as_IN","es_BR","zh_HK","khq_ML","sw_KE","en_SB","th_TH","rw_RW","chr_US","shi_MA_#Tfng","ar_IQ","nyn","yue","jmc","en_MW","naq_NA","mk","en_IO","ar_QA","en_DE","pa__#Arab","en_CC","bs","ro_MD","en_FI","pt_PT","fy","az_AZ_#Cyrl","th","dav_KE","ckb_IQ","shi_MA_#Latn","es_CU","ar","en_SC","en_VI","haw","eu_ES","en_UG","en_NZ","dje","es_UY","bas","mas_KE","ru_UA","sg_CF","el_GR","yav_CM","uz__#Latn","sg","da_GL","en_FJ","de_LI","en_BB","km_KH","smn_FI","hr_BA","de_AT","ckb_IR","nl","lu_CD","ca_ES_VALENCIA","ar_001","so_SO","lv_LV","ckb","es_CR","fr_GA","ar_KW","sr","ar_LY","sr_RS_#Cyrl","bem","en_MU","da","wae","gl_ES","en_IM","az_AZ_#Latn","en_LS","ig","en_HK","en_GI","ce_RU","en_CA","gd","ka_GE","fr_SY","sw_TZ","fr_RW","so_ET","nl_BE","ar_DJ","mg_MG","cy_GB","en_VG","cu","os_RU","sr_RS_#Latn","en_TC","ky_KG","sv_AX","af_NA","vun","en_IN","lu","ki","yo","es_NI","nb","ff_MR","sd_PK","mas_TZ","ti","kok","ewo","ms_BN","ccp_IN","br_FR"]
If I do the same in native mode :
mvn clean package -Pnative && ./target/QuarkusLocale-1.0-SNAPSHOT-runner
I obtain only one locale :
["fr_US"]
How can I obtain all available locale ?
Moreover, because of this behavior I have another problem. As you can see if I tried to develop a service who want use locale. Locale is not use and I can't apply currency format. To demonstrate that, I develop CurrencyController.
#Path("/currency")
public class CurrencyController {
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public String get(String localeString) {
String[] localeArray = localeString.split("-");
Locale locale = new Locale(localeArray[0], localeArray[1]);
NumberFormat numberFormat = NumberFormat.getCurrencyInstance(locale);
numberFormat.setCurrency(Currency.getInstance("USD"));
return numberFormat.format(1337);
}
}
You can call it with curl :
curl -X POST http://localhost:8080/api/currency -H "Content-Type: application/json" -d "en-GB"
> US$1,337.00
curl -X POST http://localhost:8080/api/currency -H "Content-Type: application/json" -d "en-US
> $1,337.00
If I do the same in native mode :
curl -X POST http://localhost:8080/api/currency -H "Content-Type: application/json" -d "en-GB"
> US$1,337.00
curl -X POST http://localhost:8080/api/currency -H "Content-Type: application/json" -d "en-US
> US$1,337.00
Running Quarkus native-image plugin on GraalVM Version 19.3.1 CE
Quarkus 1.4.1.Final
This is a very well-known issue on GraalVM. Currently, the only way to bypass it is to create Feature that will scan all locales at run time:
Add maven dependency
<dependency>
<groupId>org.graalvm.nativeimage</groupId>
<artifactId>svm</artifactId>
<version>19.3.1</version>
</dependency>
Create Feature class
package org.example;
import com.oracle.svm.core.annotate.AutomaticFeature;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.impl.RuntimeClassInitializationSupport;
import java.util.Locale;
#AutomaticFeature
public class NativeLanguageFeature implements Feature {
#Override
public void beforeAnalysis(BeforeAnalysisAccess access) {
ImageSingletons.lookup(RuntimeClassInitializationSupport.class).initializeAtBuildTime(SupportedLocales.class, "__");
final Locale[] availableLocales = Locale.getAvailableLocales();
final SupportedLocales supportedLocales = new SupportedLocales();
supportedLocales.locales = availableLocales;
ImageSingletons.add(SupportedLocales.class, supportedLocales);
}
public static class SupportedLocales {
public Locale[] locales;
}
}
In a place where you want to access locales, check if SupportedLocales present in ImageSingletons
#GET
#Produces(MediaType.APPLICATION_JSON)
public Locale[] get() {
if(ImageSingletons.contains(NativeLanguageFeature.SupportedLocales.class)){
final NativeLanguageFeature.SupportedLocales lookup = ImageSingletons.lookup(NativeLanguageFeature.SupportedLocales.class);
return lookup.locales;
}
return Locale.getAvailableLocales();
}
You probably will need to do same thing with money as well.
This happens to keep the native executable small as possible, so only the default locale is added to the bundle. The GraalVM allows you to add more custom locales at build time with -H:IncludeLocales=fr,en,....
More information in https://www.graalvm.org/22.1/reference-manual/native-image/Resources/#locales
Related
How to publish a message to Kafka topic using spring cloud stream in reactive way [using webflux]?
Publish a message to kafka topic without using StreamBridge as it uses deprecated components.
Using reactor API: All you need to do is declare a Supplier<Flux<whatever>> which returns EmitterProcessor from the reactor API (see Reactive Functions support for more details) to effectively provide a bridge between the actual event source (foreign source) and spring-cloud-stream. All you need to do now is feed the EmitterProcessor with data via EmitterProcessor#onNext(data) operation. Quoted from spring cloud stream docs #SpringBootApplication #Controller public class WebSourceApplication { public static void main(String[] args) { SpringApplication.run(WebSourceApplication.class); } EmitterProcessor<String> processor = EmitterProcessor.create(); #RequestMapping #ResponseStatus(HttpStatus.ACCEPTED) public void delegateToSupplier(#RequestBody String body) { processor.onNext(body); } #Bean public Supplier<Flux<String>> supplier() { return () -> this.processor; } } To send a message use curl curl -H "Content-Type: text/plain" -X POST -d "hello from the other side" http://localhost:8080/
How to configure Selenide remote grid url in Selenium-Jupiter test framework?
I am trying to configure a Selenide driver within the Selenium-Jupiter framework, to use my remote grid url but it keeps ignoring the configuration, and just runs the local installed browser. Here is how I am trying to configure it. Any idea what might be wrong here? import com.codeborne.selenide.Configuration; import com.codeborne.selenide.SelenideConfig; import io.github.bonigarcia.seljup.SelenideConfiguration; import static com.codeborne.selenide.Browsers.CHROME; public abstract class ChromeTest extends BaseTest { #SelenideConfiguration SelenideConfig selenideConfig = new SelenideConfig(); private String getSeleniumRemote() { System.getProperty("selenide.remote", ""); } public ChromeTest() { if (getSelenideRemote().isEmpty()) { selenideConfig.proxyEnabled(false) .browser(CHROME).startMaximized(false) .browserSize("800x1200").browserPosition("50x60"); } else { Configuration.timeout = 6000; Configuration.remote = getSelenideRemote(); selenideConfig.proxyEnabled(false) .startMaximized(true).browser(CHROME); } } } I know the regular RemoteWebDriver works and I can get that working but I am hoping to use Selenide in the above example: Example: #Test void testWithRemoteSelenide(#DriverUrl("http://127.1:4444/wd/hub") #DriverCapabilities("browserName=" + CHROME) SelenideDriver driver) I can get it to work with the annotation, but the problem is that I need that annotation to be conditional on passing a param to the tests. I want to be able to easily switch using grid or local. Thanks for your help anyone.
Ok, after almost 48 hours an no reponse, I finally figured out the solution. Here it is: //build.gradle test { useJUnitPlatform() ignoreFailures = false beforeTest { descriptor -> logger.lifecycle("Running test: $descriptor.className") } systemProperty "env", System.getProperty("env") def remote = System.getProperty("selenide.remote", "") if (!remote.isEmpty()) { systemProperty("selenide.remote", remote) } } Then, in my test base class: public abstract class ChromeTest extends BaseTest { #SelenideConfiguration SelenideConfig selenideConfig = new SelenideConfig(); /** * This config is equivilant to the documented method: * Example: test(#DriverUrl("http://127.1:4444/wd/hub") * #DriverCapabilities("browserName=chrome") SelenideDriver sd) */ public ChromeFormTest() { if (getSelenideRemote().isEmpty()) { selenideConfig.proxyEnabled(false).proxyHost("http://proxy.domain.com") .proxyPort(8080) .browser(CHROME).startMaximized(false) .browserSize("800x1200").browserPosition("50x60"); } else { Configuration.timeout = 6000; Configuration.remote = getSelenideRemote(); selenideConfig.proxyEnabled(false).proxyHost("http://proxy.domain.com") .proxyPort(8080) .startMaximized(false).browser(CHROME); } } } Then, when I execute, it looks like this: gradle clean test -Denv=sys -Dselenide.remote=http://127.1:4444/wd/hub --info --tests com.qa.suite.* And the constructor of each test looks like: #Test public void testWhatever(SelenideDriver sd) {
In Spring Boot and Kotlin how can you properly add internationalization?
I have been following this tutorial and I can follow it, although I had some issues. Almost at the end, where it talks about configuration properties, I though it was a good idea to try to set some kind of internationalization to it, with a different file depending on the language, but I have been unable to do so. I only load it on English, no matter how I try.
Have you read the Spring Boot Docs on Internationalization? I guess it is not that helpful. Suppose you have your resource bundle like this. - src/main/resources/ - messages.properties - messages_es.properties - ... You need to add some configuration: #Configuration class AppConfig : WebMvcConfigurer { #Bean fun localeResolver() = SessionLocaleResolver().apply { setDefaultLocale(Locale.ENGLISH) } #Bean fun localeInterceptor() = LocaleChangeInterceptor().apply { this.paramName = "lang" } override fun addInterceptors(registry: InterceptorRegistry) { registry.addInterceptor(localeInterceptor()) } } Then an example using greet.hello might look like: import org.springframework.context.MessageSource import org.springframework.context.i18n.LocaleContextHolder import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RestController #RestController class PageController(val messageSource: MessageSource) { #GetMapping("/greet") fun greet(): String { return messageSource.getMessage("greet.hello", null, LocaleContextHolder.getLocale()) } } If you do not think this looks pretty, then try using some of Kotlin's features to clean it up. This is how to choose a language: # Use the default curl --request GET --url 'http://localhost:8080/greet' # Hello # Use English curl --request GET --url 'http://localhost:8080/greet?lang=en' # Hello # Use Spanish curl --request GET --url 'http://localhost:8080/greet?lang=es' # Hola
Spring and Azure function
Does Spring work with Azure functions? For example: Rest API that the code inside uses "Autowired" annotation (After running mvn azure-functions:run I've got NullPointerException on "myScriptService"). import java.util.*; import com.microsoft.azure.serverless.functions.annotation.*; import com.microsoft.azure.serverless.functions.*; import com.company.ScriptService; import org.springframework.beans.factory.annotation.Autowired; /** * Azure Functions with HTTP Trigger. */ public class Function { #Autowired ScriptService myScriptService; /** * This function listens at endpoint "/api/hello". Two ways to invoke it using "curl" command in bash: * 1. curl -d "HTTP Body" {your host}/api/hello * 2. curl {your host}/api/hello?name=HTTP%20Query */ #FunctionName("myhello") public HttpResponseMessage<String> hello( #HttpTrigger(name = "req", methods = "post", authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request, final ExecutionContext context) { context.getLogger().info("Java HTTP trigger processed a request."); // Parse query parameter String query = request.getQueryParameters().get("name"); String name = request.getBody().orElse(query); if (name == null) { return request.createResponse(400, "Please pass a name on the query string or in the request body"); } else { return request.createResponse(200, "Hello, " + name + ", myScriptService.isEnabled(): " + myScriptService.isEnabled()); } } }
As some asked for a solution in the comments above, I'm assuming that this problem might be of relevance for other users, too. So I think Spring Cloud Function is the magic word here: besides some other points (see the project page for details), it aims to enable Spring Boot features (like dependency injection, what you're looking for) on serverless providers (besides Azure Functions, also AWS Lambda and Apache OpenWhisk are supported). So you have to make some modifications to your project: Add the spring-cloud-function-adapter-azure dependency: <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-function-adapter-azure</artifactId> <version>2.0.1.RELEASE</version> </dependency> Your handler class needs some additional code: Add the #SpringBootApplication annotation Add the main() method known from Spring Boot applications Make sure that Spring can find your ScriptService class e. g. by using the #ComponentScan annotation It should look like this: #SpringBootApplication #ComponentScan(basePackages = { "package.of.scriptservice" }) public class Function { #Autowired ScriptService myScriptService; #FunctionName("myhello") public HttpResponseMessage<String> hello( #HttpTrigger(name = "req", methods = "post", authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request, final ExecutionContext context) { // Your code here } public static void main(String[] args) { SpringApplication.run(DemoFunctionHandler.class, args); } } You can find a full example here and here
It looks like that there are a lot of changes between spring cloud v1 and v2. Have a quick look at this blog post: https://spring.io/blog/2018/09/25/spring-cloud-function-2-0-and-azure-functions If you build your project like the example, spring will create the spring boot context when the azure function is called (and you call handleRequest). But the spring context is not available before this.
Do you add your package to scan for spring cloud function ? spring.cloud.function.scan.packages="yourPackage" It is to add in your application.properties
UI Automator error
I am new to UI Automator and trying to run script but getting error.
I use Android studio: #RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { protected UiDevice uiDevice; public ExampleInstrumentedTest() { uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); } #Test public void functionTest() { uiDevice.pressBack(); } } In your Example to try edit: uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); #RunWith(AndroidJUnit4.class) #Test And then if you want run script i will use this command: adb shell am instrument -w -r -e class com.android.test.Lunch#TestDemo com.android.test.test/android.support.test.runner.AndroidJUnitRunner