In Spring Boot and Kotlin how can you properly add internationalization? - spring
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
Related
GraalVM Quarkus Locale in native mode
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
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
Don't have access to Spring boot controller, but have access to index.html
I have a Spring Boot project, what I deployed on remote server. When I call http://109.206.178.66:8080/proxynator/ - I can see an index.html, but when I call rest controller by url http://109.206.178.66:8080/proxynator/countries - I have an error 404 whith Description: The origin server did not find a current representation for the target resource or is not willing to disclose that one exists. It's strangely, because when I start this project on my local machine, all working right. Project structure is: Line from catalina.out file: SEVERE [http-nio-8080-exec-19] org.apache.tomcat.util.descriptor.web.WebXmlParser.parseWebXml Parse error in application web.xml file at [file:/opt/tomcat/webapps/proxynator/WEB-INF/web. My controller: #RestController class MainController(val proxyRepo: ProxyRepo, val countryRepo: CountryRepo) { #GetMapping("/countries") fun findCountries(): List<Country> = countryRepo.findAll() #GetMapping("/proxies") #ResponseBody fun findProxies(#RequestParam countryid: Int): List<Proxy> = proxyRepo.findAllByCountryid(countryid) } UPD I added configuration class on the root package #Configuration open class RestConfiguration { #Bean open fun mainController():MainController = MainController() } But it does not working. I don't understand, why I can see index.html by irl http://109.206.178.66:8080/proxynator/, but can't get access to my controllers. When I package my project I have some files: Any advice?
your api access url is wrong access using http://109.206.178.66:8080/countries this is the correct path for your rest mapping, not http://109.206.178.66:8080/proxynator/countries
To solve this problem I was edit my Application class. #SpringBootApplication #EnableAutoConfiguration open class Application: SpringBootServletInitializer(){ #Override override fun configure(application: SpringApplicationBuilder?): SpringApplicationBuilder { return super.configure(application) } } fun main(args: Array<String>) { SpringApplication.run(Application::class.java, *args) } I was override configure method, and all is working! Hope it will save time for anybody!
Microservice with Spring Boot
I'm working in Windows 7. I've Spring CLI v1.5.3.RELEASE installed. In a working directory, using command spring init --build maven --groupId com.redhat.examples --version 1.0 --java-version 1.8 --dependencies web --name hola-springboot hola-springboot I created holo-springboot app. Then navigated to hola-springboot directory,ran $ mvn spring-boot:run The application run. Going to http://localhost:8080, I do see Whitelabel error page. Whereafter, I tried to add helloworld fuctionality. That is, in the app, in the packeage com.example, I included the following java class. #RestController #RequestMapping("/api") public class HolaRestController { #RequestMapping(method = RequestMethod.GET, value = "/hola", produces = "text/plain") public String hola() throws UnknownHostException { String hostname = null; try { hostname = InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { hostname = "unknown"; } return "Hola Spring Boot de " + hostname; } } Re-built from hola-springboot dircetory, mvn clean package I get build failure as at https://pastebin.com/77Ru0w52 I'm unable to figure out. Could somebody help? I'm following the book Microservices for Java Developers by Christian Posta, Chapter 2, available free at developers Redhat.
Looks like you are missing a dependency on spring boot starter web in your maven pom.xml file https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web/1.5.3.RELEASE. Or you are not importing the classes correctly.
You are accessing http://localhost:8080 but you have defined a mapping in your rest controller "/hola". So you will have to access the url http://localhost:8080/hola as you do not have any default method in your rest controller.
BuildFailure shows that you have not given import statements in you Class. statements missing are the below import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import java.net.InetAddress; import java.net.UnknownHostException; include these and you will be fine.
Running cucumber-groovy features against a spring boot api
I've been attempting to get cucumber-groovy working with spring-boot, but it's not been going well. I get the error org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8080/applicants": Connection refused; nested exception is java.net.ConnectException: Connection refused which seems to indicate that it's hitting the endpoint, but that the service isn't running. I've read that I need to have a cucumber.xml file, but my project is not using any xml config, it's all annotations, so instead I've got this: package support import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; #Configuration #ComponentScan(basePackages = "com.base.package") public class CucumberConfiguration {} I've added it to the World, but this seems to be the wrong way of doing things (i.e. I don't know how to add an annotation on groovy step defs). package support import com.thing.app.Application import org.junit.runner.RunWith import org.springframework.boot.test.SpringApplicationContextLoader import org.springframework.boot.test.WebIntegrationTest import org.springframework.test.context.ContextConfiguration import org.springframework.test.context.junit4.SpringJUnit4ClassRunner import org.springframework.test.context.web.WebAppConfiguration import static cucumber.api.groovy.Hooks.* //#RunWith(SpringJUnit4ClassRunner) //#ContextConfiguration(classes = Application, loader = SpringApplicationContextLoader) //#WebAppConfiguration //#WebIntegrationTest #ContextConfiguration(classes = CucumberConfiguration) public class AbstractTest { } World() { new AbstractTest() } Before() {} After() {} I left in my other annotations to kind of show what I've done so far. None of it has worked. I've also tried setting up an AbstractDefs class as seen here https://github.com/jakehschwartz/spring-boot-cucumber-example/tree/master/src/test/java/demo, but that also hasn't worked, mostly because I'm not using the cucumber-java style of things, but instead the cucumber-groovy style, which doesn't use step definition classes. Edit: Just discovered I was doing things wrong by having an env.groovy, I'm used to the ruby cucumber, so I'm having trouble finding all the little problems. Still am having the same issue though, I don't know how to execute in a Spring context.
You can instantiate Spring test context with io.cucumber.spring.SpringFactory and register adapter in World to allow groovy script has access to Spring beans: env.groovy: #ContextConfiguration(classes = TestConfiguration, loader = SpringBootContextLoader) class CucumberContextConfiguration { } //adapter bypassing World properties to Spring context class SpringFactoryWorldAdapter { private final SpringFactory factory; SpringFactoryWorldAdapter(SpringFactory factory) { this.factory = factory; } #Override Object getProperty(String s) { return factory.testContextManager.getContext().getBean(s); } } def factory; //Keep state to prevent repeated context initialization World { args -> if (factory == null) { factory = new SpringFactory() factory.addClass(CucumberContextConfiguration) factory.start() } new SpringFactoryWorldAdapter(factory) }