Using Configuration Interface or AppContext.getProperty

Hi all,
I’m trying to implement an address-completion service using Google Geocoding API. It works fine in dispalying map and I wrote GoogleAPI key in charts.map.apiKey in web-app.properties which I want to get in my Service.

following this example

https://doc.cuba-platform.com/manual-6.9/app_properties.html

First I tried with AppContext.getProperty specifying String “charts.map.apiKey” with no success.

Then I tryied Configuration Interface creating ContactsConfig Interface (code follows) in global module

------ Extended Config Interface ----------------------------

package pro.sysonline.contacts.global;

import com.haulmont.cuba.core.config.Config;
import com.haulmont.cuba.core.config.Property;
import com.haulmont.cuba.core.config.Source;
import com.haulmont.cuba.core.config.SourceType;

@Source(type = SourceType.APP)
public interface ContactsConfig extends Config {

    @Property("contacts.googleApiKey")
    String getGoogleApiKey();
}

to Inject in my service module:

 @Inject
    protected ContactsConfig configuration

Both method doe’nt seem to work.
My doubt are:

AppContext:
do I have to check if application is totally initialized with a listener before using it. Application is usable so I presume it is initialized.

Config:
property annotation in extended interface hase the form ProjectName.PropertyName where PropertyName seems to be one level. For property names like charts.map.apiKey correct name is:

ProjectName.charts.map.apiKey and getterPropertyName must be as complex as PropertyName?

getProjectCharrtsMapApiKey() for example?

I tried to duplicate property with one level but still no succes. What Im’doing wrong?

Thank you in advance.
Fabrizio

Hi Fabrizio,

The problem here is that the service on the middle tier cannot get properties from the client tier. One of the possible solutions is using a managed bean in the web module instead of a service and using the configuration interface from it, for example:

import com.haulmont.charts.core.global.MapConfig;
import org.springframework.stereotype.Component;

import javax.inject.Inject;

@Component(GeoBean.NAME)
public class GeoBean {

    @Inject
    private MapConfig mapConfig;

    public static final String NAME = "sales_GeoBean";

    public String getMyApiKey() {
        return mapConfig.getApiKey();
    }
}

You also can get the property value right from screen controllers, from any source:

String apiKey = mapConfig.getApiKey();
String apiKey = geoBean.getMyApiKey();
String apiKey = AppContext.getProperty("charts.map.apiKey");

Hi Olga,
let me know if I understand correctly:

  1. Cuba Platform has two different app (java app in tomcat environment) one for middle tier and one for web interface so configuration file ‘app.properties’ refers middle-tier, ‘web-app,properties’ is for web client application. (eventually there are more for portal,desktop (swing),polymer (even if the last, I suppose, is full Javascript tecnology)

  2. MAP component obviously is a web-client component so property chart…ApiKey is simply for Map component used in web-client application. I can’t get it in middle tier, because this is a different java-appiication (with different configuration file and executing in a separate application environment) in tomcat deployment, which properties can be directly accessed only from classes in client application (screen controllers & C.).
    If I want to use it in middle tier I can copy (better with different name) in app.properties, or use a database property (so SOURCE=DATABASE in Config derived interface). Last solution should work because database is a shared resource between middle and client tier.

  3. com.haulmont.charts.core.global.MapConfig is a derived Config interface specifically for MAP component,I suppose, ready to be used in client tier, natural location of Map component that is a web-interface component. Alternatively I have to move service code in managed bean in web-client tomcat- application, not necessarly loosing ability to use code functionality from every windows controller in client application (thanks to managed bean, that is essentially a bean managed by a Spring directory). If I need database access in the same code I have to use DataManager instead of Entity Manager. The last is an alternative implementation of service/middle-tier functionalities using web-client code with limits that can’t be used by other application components, like polymer interface for example.

I hope to have understood your solution.
Thank you for your advice,
Fabrizio

1 Like

Fabrizio, you got the idea, everything is absolutely right.
By the way, for the point 2: you can create your own Configuration interface (instead of MapConfig) in the global module, so that it can be reached from all tiers, and access the property with SourceType.DATABASE from it.

Hi Olga,
thank you very much

To set property in case I use SourceTyep.DATABASE and readonly property (only getter declared in myInterface) I suppose I can user a simple SQL editor, given that this kind of parameter is a global parameter and I do not want to be modifiable by users.

There’s a simpler way: even if you declare no setter, you can still set the value of the property via JMX console or the Application properties screen in the application UI (Administration menu).
image

OK, very nice and interesting

Thx

Some more info:

  1. to create new DB property first time (as described by Olga) you have necessarly to use jmx console
    Storing Properties in the Database - CUBA Platform. Developer’s Manual

  2. it can be retrieved or update both by jmx console or Administration-> Application Properties.
    By this tools All properties can be edited (not only Database ones) but only database properties can be
    persisted to survive to tomcat restart, file properties values change in memory, but must be modifyed in
    files

1 Like

Hi Olga it worked fine till I shutdown Server.
When I restarted it didn’t startup again.

This my implementation. My Config Interface:

package pro.sysonline.contacts.global;

import com.haulmont.cuba.core.config.Config;
import com.haulmont.cuba.core.config.Property;
import com.haulmont.cuba.core.config.Source;
import com.haulmont.cuba.core.config.SourceType;

@Source(type = SourceType.DATABASE)
public interface IContactsConfig extends Config {

    @Property("googleApiKey")
    String getGoogleApiKey();
}

Extract from my Service Bean (Middleware)

@CompileStatic
@Service(AddressGoogleCompleteService.NAME)
class AddressGoogleCompleteServiceBean implements AddressGoogleCompleteService { 

...... some more ......

@Inject IContactsConfig configuration

First Try

@PostConstruct all Bean used by this one should be initialized, even if APP is’nt totally

@PostConstruct
private void initVariables() {
       GoogleAPIKey = configuration.getGoogleApiKey()
}
2018-11-05 19:09:39.247 ERROR [localhost-startStop-1] com.haulmont.cuba.core.sys.AbstractWebAppContextLoader - Error initializing application
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'contacts_AddressGoogleCompleteService': Invocation of init method failed; nested exception is java.lang.IllegalStateException: ApplicationContext is not initialized
 at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:137) ~[spring-beans-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:409) ~[spring-beans-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1626) ~[spring-beans-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555) ~[spring-beans-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483) ~[spring-beans-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:312) ~[spring-beans-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:308) ~[spring-beans-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761) ~[spring-beans-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867) ~[spring-context-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543) ~[spring-context-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:139) ~[spring-context-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:93) ~[spring-context-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at com.haulmont.cuba.core.sys.CubaClassPathXmlApplicationContext.(CubaClassPathXmlApplicationContext.java:27) ~[cuba-global-6.10.3.jar:6.10.3]
 at com.haulmont.cuba.core.sys.CubaCoreApplicationContext.(CubaCoreApplicationContext.java:26) ~[cuba-core-6.10.3.jar:6.10.3]
 at com.haulmont.cuba.core.sys.AppContextLoader.createApplicationContext(AppContextLoader.java:89) ~[cuba-core-6.10.3.jar:6.10.3]
 at com.haulmont.cuba.core.sys.AppContextLoader.createApplicationContext(AppContextLoader.java:35) ~[cuba-core-6.10.3.jar:6.10.3]
 at com.haulmont.cuba.core.sys.AbstractAppContextLoader.initAppContext(AbstractAppContextLoader.java:62) ~[cuba-global-6.10.3.jar:6.10.3]
 at com.haulmont.cuba.core.sys.AbstractWebAppContextLoader.contextInitialized(AbstractWebAppContextLoader.java:77) ~[cuba-global-6.10.3.jar:6.10.3]
 at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4745) [catalina.jar:8.5.23]
 at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5207) [catalina.jar:8.5.23]
 at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) [catalina.jar:8.5.23]
 at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:752) [catalina.jar:8.5.23]
 at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:728) [catalina.jar:8.5.23]
 at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:734) [catalina.jar:8.5.23]
 at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:1144) [catalina.jar:8.5.23]
 at org.apache.catalina.startup.HostConfig$DeployDirectory.run(HostConfig.java:1878) [catalina.jar:8.5.23]
 at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_191]
 at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_191]
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_191]
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_191]
 at java.lang.Thread.run(Thread.java:748) [na:1.8.0_191]
Caused by: java.lang.IllegalStateException: ApplicationContext is not initialized

I Found This:

Hi,
You cannot access Configuration bean from another beans if your config has DATABASE source. You can do this only after context start:

https://doc.cuba-platform.com/manual-6.9/app_start_recipe.html?_ga=2.233700318.1178846495.1541433546-1676116619.1541177925
https://doc.cuba-platform.com/manual-6.9/app_lifecycle_events.html?_ga=2.233700318.1178846495.1541433546-1676116619.1541177925

So I added listener for Application LyfeCycle Events:
Second Try:

// The Elegant way.....

@Order(Events.LOWEST_PLATFORM_PRECEDENCE)
    @EventListener
    private void handleAppContextStartedEvent(AppContextStartedEvent event) {
        GoogleAPIKey = configuration.getGoogleApiKey()
    }

// ----- or in a Different way

class AddressGoogleCompleteServiceBean implements AddressGoogleCompleteService, ApplicationListener<AppContextStartedEvent> {   <<<--- Implement one more interface

@Override  <<<--- Implement one more interface
    void onApplicationEvent(AppContextStartedEvent event) {
        GoogleAPIKey = configuration.getGoogleApiKey()
    }

I got this error:

-------APP LOG---------    
2018-11-05 19:26:45.503 ERROR [localhost-startStop-1] com.haulmont.cuba.core.sys.AbstractWebAppContextLoader - Error initializing application
org.springframework.beans.factory.BeanInitializationException: Failed to process @EventListener annotation on bean with name 'contacts_AddressGoogleCompleteService'; nested exception is java.lang.IllegalStateException: Need to invoke method 'handleAppContextStartedEvent' declared on target class 'AddressGoogleCompleteServiceBean', but not found in any interface(s) of the exposed proxy type. Either pull the method up to an interface or switch to CGLIB proxies by enforcing proxy-target-class mode in your configuration.
 at org.springframework.context.event.EventListenerMethodProcessor.afterSingletonsInstantiated(EventListenerMethodProcessor.java:106) ~[spring-context-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:781) ~[spring-beans-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867) ~[spring-context-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543) ~[spring-context-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:139) ~[spring-context-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:93) ~[spring-context-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at com.haulmont.cuba.core.sys.CubaClassPathXmlApplicationContext.(CubaClassPathXmlApplicationContext.java:27) ~[cuba-global-6.10.3.jar:6.10.3]
 at com.haulmont.cuba.core.sys.CubaCoreApplicationContext.(CubaCoreApplicationContext.java:26) ~[cuba-core-6.10.3.jar:6.10.3]
 at com.haulmont.cuba.core.sys.AppContextLoader.createApplicationContext(AppContextLoader.java:89) ~[cuba-core-6.10.3.jar:6.10.3]
 at com.haulmont.cuba.core.sys.AppContextLoader.createApplicationContext(AppContextLoader.java:35) ~[cuba-core-6.10.3.jar:6.10.3]
 at com.haulmont.cuba.core.sys.AbstractAppContextLoader.initAppContext(AbstractAppContextLoader.java:62) ~[cuba-global-6.10.3.jar:6.10.3]
 at com.haulmont.cuba.core.sys.AbstractWebAppContextLoader.contextInitialized(AbstractWebAppContextLoader.java:77) ~[cuba-global-6.10.3.jar:6.10.3]
 at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4745) [catalina.jar:8.5.23]
 at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5207) [catalina.jar:8.5.23]
 at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) [catalina.jar:8.5.23]
 at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:752) [catalina.jar:8.5.23]
 at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:728) [catalina.jar:8.5.23]
 at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:734) [catalina.jar:8.5.23]
 at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:1144) [catalina.jar:8.5.23]
 at org.apache.catalina.startup.HostConfig$DeployDirectory.run(HostConfig.java:1878) [catalina.jar:8.5.23]
 at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_191]
 at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_191]
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_191]
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_191]
 at java.lang.Thread.run(Thread.java:748) [na:1.8.0_191]
Caused by: java.lang.IllegalStateException: Need to invoke method 'handleAppContextStartedEvent' declared on target class 'AddressGoogleCompleteServiceBean', but not found in any interface(s) of the exposed proxy type. Either pull the method up to an interface or switch to CGLIB proxies by enforcing proxy-target-class mode in your configuration.
 at org.springframework.core.MethodIntrospector.selectInvocableMethod(MethodIntrospector.java:135) ~[spring-core-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.aop.support.AopUtils.selectInvocableMethod(AopUtils.java:130) ~[spring-aop-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.context.event.EventListenerMethodProcessor.processBean(EventListenerMethodProcessor.java:155) ~[spring-context-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.context.event.EventListenerMethodProcessor.afterSingletonsInstantiated(EventListenerMethodProcessor.java:103) ~[spring-context-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 ... 24 common frames omitted
 -------APP LOG---------

Both are initialization/lifeCycle problems.

Whats wrong with Appdatacontext Listener?

Some more resources about Spring Events included in Cuba Platform:

https://doc.cuba-platform.com/manual-6.9/events.html
https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2

This is a sort of general Application BUS based on Observer Pattern.

Thank you in advance,
Fabrizio

Don’t access other beans in @PostConstruct, as they may use some mechanisms not initialized yet. Move your code to an AppContextStartedEvent listener, when the whole application is initialized.

Regards,
Konstantin

2 Likes

Sorry, I can’t reply, it says this…

you can only mention 2 users in a post

Use ``` symbol for code blocks and you will be able to use @ symbol for annotations in posts.

Sorry, I did it but there was a spurious At in comment.

Hi Konstantin, thank you for your quick reply.
I Tried this:

@Order(Events.LOWEST_PLATFORM_PRECEDENCE)
@EventListener(AppContextStartedEvent.class)
protected void appStarted(AppContextStartedEvent event) {
    GoogleAPIKey = configuration.getGoogleApiKey()
}

but I get this strange error: (app.log, I’m using 6.10.3 framework release)

2018-11-07 17:19:58.355 ERROR [localhost-startStop-1] com.haulmont.cuba.core.sys.AbstractWebAppContextLoader - Error initializing application
org.springframework.beans.factory.BeanInitializationException: Failed to process @EventListener annotation on bean with name 'contacts_AddressGoogleCompleteService'; nested exception is java.lang.IllegalStateException: Need to invoke method 'appStarted' declared on target class 'AddressGoogleCompleteServiceBean', but not found in any interface(s) of the exposed proxy type. Either pull the method up to an interface or switch to CGLIB proxies by enforcing proxy-target-class mode in your configuration.
 at org.springframework.context.event.EventListenerMethodProcessor.afterSingletonsInstantiated(EventListenerMethodProcessor.java:106) ~[spring-context-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:781) ~[spring-beans-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867) ~[spring-context-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543) ~[spring-context-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:139) ~[spring-context-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:93) ~[spring-context-4.3.18.RELEASE.jar:4.3.18.RELEASE]
 at com.haulmont.cuba.core.sys.CubaClassPathXmlApplicationContext.(CubaClassPathXmlApplicationContext.java:27) ~[cuba-global-6.10.3.jar:6.10.3]

Only reference in documentation for this error that is this “compact” representation using org.springframework.context.event.EventListener annotation we can’t use this in JMX Beans, but this is a @Service bean, so I suppose is a managed bean like @Component (in your example).

I also tried this (explicit listener form, no annotation)

@Override
void onApplicationEvent(AppContextStartedEvent event) {
    GoogleAPIKey = configuration.getGoogleApiKey()
}

implementing also: ApplicationListener<AppContextStartedEvent> in @ServiceBean, but same error.
It seems a bad proxy class is created without a referring interface.

Thx,
Fabrizio

Hi Fabrizio,

It would work if you define this method in the service interface, but it’s obviously a bad practice. Another workaround is to define the listener in a separate bean without interface.

But I would suggest abandoning the idea of getting GoogleAPIKey from the config at startup - it’s cached anyway so you can just inject your configuration interface into the service and invoke configuration.getGoogleApiKey() when needed in your code. No database roundtrips will be made except the first invocation.

Thank you Kostantin,
now it works fine.

Just to better understand:
a rule of thumb is not to initialize bean properties with reference to other beans, because the global sequence of bean creation/initialization is not predictable. If I use beans (including Configuration) in an application method, all beans are definitively loaded (if not application could not work). I do not incur in performance degradation due to ORM cache (I suppose).

Fabrizio