could you suggest any working tip, how to change order of services in OSGI test environment without modifying of tested classes and generated descriptors?
I am testing a custom class, initialized in test OSGI environment, org.apache.sling.testing.mock.osgi.junit.OsgiContext, with a List of multiple service instances implementing the same interface. Order of service instances is specified statically with SERVICE_RANKING property annotation and generated xml descriptors in OSGI-INF folder.
I would like to simulate switch of the SERVICE_RANKING and the oder of services in local test environment in the way, how service order could be switched in the real OSGI container.
Service class example:
#Component
#Service(MyService.class)
public class PlainService implements MyService {
#Property(label = "Service Ranking", intValue = PlainService.DEFAULT_RANKING,
description = "set lower value to get higher priority of service",
propertyPrivate = false)
static final String PROPERTY_RANKING = Constants.SERVICE_RANKING;
static final int DEFAULT_RANKING = 999;
...
}
Service container class example:
#Component
#Service(MyService.class)
public class PlainServiceContainer implements MyServiceContainer {
#Reference(name = "myService", referenceInterface = MyService.class,
cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC)
private final RankedServices<MyService> myService = new RankedServices<>();
...
void bindMyService(MyService service, Map<String, Object> props) {
...
}
void unbindMyService(MyService service, Map<String, Object> props) {
...
}
}
I expect there could be a property definition, which have to change SERVICE_RANKING at the moment of service initialization:
OsgiContext context = new OsgiContext();
context.registerInjectActivateService(new PlainService(), ImmutableMap.of(Constants.SERVICE_RANKING, 989));
The actual example with ranking 989 does not work and service is still initialized with ranking 999, but there should be working case, which changes it.
Related
I'm creating a Java application that depending on certain conditions/configurations it instantiates a SpringBoot application to receive some HTTP messages.
My problem is that I have a ReceiverService that needs to use some variables that are created outside of the SpringBoot application. Is there a way to pass local java variables (from inside the application, not outside like the shell or files) to SpringBoot components?
Example:
I have a Manager object that, depending on some conditions, it defines a variable param that I want to use in the SpringBoot Component ReceiverService.
public class Manager {
public Manager(bool condition) {
String param = "foo";
if (condition) {
param = "bar";
}
ReceiverApp receiver = new ReceiverApp(); // init SpringBoot app
}
}
The SpringBoot app:
#SpringBootApplication
public class ReceiverApp {
public ReceiverApp() {
SpringApplication.run(ReceiverApp.class);
}
#Component
public class ReceiverService implements InitializingBean {
final CustomObject obj1 = new CustomObject(param);
#Override
public void aFunction() throws Exception {
MyConfig config = MyConfig.build(param);
}
}
SpringApplication.run is designed to accept key=value pairs like the ones you give in command line parameters to the main method in Java.
Since you seem to have the other Spring boot application jar in your class-path, and seem to be able to just instanciate the ReceiverApp, you could just pass the parameters as strings (of the format String[]{"key1=value1", "key2=value2"})
These can be passed to SpringApplication.run, and these will automatically become Spring configuration values, which can be injected anywhere in the application.
#SpringBootApplication
public class ReceiverApp {
public ReceiverApp(String[] notReallyFromCommandLineArgs) {
SpringApplication.run(ReceiverApp.class, notReallyFromCommandLineArgs);
}
You can send the parameters like this:
String[] params = new String[]{"my.params.param1="+param1};
ReceiverApp receiver = new ReceiverApp(params);
You can inject them anywhere in that Receiver Spring application as a value.
#Value("my.params.param1")
String theParam1;
Refer:
https://stackoverflow.com/a/55890457/1364747
There are several ways to create a manager app which configures another app.
Scenario 1
A common way is to put the config in a database which is accessed directly by both apps, although this is less clean (it creates a hard dependency via the database).
Scenario 2
A cleaner solution is if the manager app owns the database, and offers a config service which is consumed (and presumably cached) by the configured app. Typically the config service would be accessed via remoting, e.g. REST, so the two apps can be deployed and run independently.
Scenario 3
If you want to keep it simple, you could reverse the setup and implement the config service in the configured app itself (sort of like the management interface in Tomcat). Then you could either add a web UI in the same app and be done with it, or build a separate standalone client which interacts with that service. In this last two scenarios, think about security because you might not want any client to be able to connect and change the config.
You can have ReceiverService inject a Supplier<String> (or define your own interface if you prefer a less generic naming) and make Manager implement it.
#Component
public class ReceiverService implements InitializingBean {
#Autowired
private Supplier<String> paramSupplier;
final CustomObject obj1 = new CustomObject(param);
#Override
public void aFunction() throws Exception {
MyConfig config = MyConfig.build(paramSupplier.get());
}
}
public class Manager implements Supplier<String> {
private final String param;
public String get() { return param; }
public Manager(bool condition) {
param = "foo";
if (condition) {
param = "bar";
}
// why is this in the Manager constructor???
ReceiverApp receiver = new ReceiverApp(); // init SpringBoot app
}
}
Or you define your own #Component to supply the parameter and have both manager and receiver inject it.
I have an application class "Application", one abstract class "AbstractClass" extended by "Impl1" and "Impl2".
The application class gets the impl1 or impl2 to perform some task based on the input it receives.
Currently I am injecting both the classes into the application class as shown below.
Then based on input, I either ask impl1 OR impl2 to perform the task.
public class Application {
private static final Data data1 = DATA_CONFIG.data_1;
private final AbstractClass impl1;
private final AbstractClass impl2;
#Inject
Application(final AbstractClass impl1, final AbstractClass impl2){
this.impl1 = impl1;
this.impl2 = impl2;
}
public void mainTask(final Data data){
if(data == data1){
impl1.performTask();
}else{
impl2.performTask();
}
}
}
But, is there any way I could use assisted inject or a similar concept to inject only the dependency required, so for example input is data1, I only inject impl1 and not impl2.??
So, what you want is to select injected object depending to some context of injection point - value of Data object in particular. I didn't do such things and can't guarantee success, but you can try custom injections.
Also you can do something like factory. But IMHO, this approach is not much better than original, cause it will just move selection between impl1 and impl2 to a factory class you have to create first.
Sketch:
#Inject
Application(IAbstractClassFactory factory){
this.factory = factory
}
void mainTask(final Data data){
impl = factory.create(data)
}
I know the default order of spring's properties sources: http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html
how can i add my own property source with a specific precedence?
#PropertySource is not enough as it adds new properties with very low priority
There are plenty of ways to do this; I'll just quote the official documentation:
A SpringApplication has ApplicationListeners and ApplicationContextInitializers that are used to apply customizations to the context or environment. Spring Boot loads a number of such customizations for use internally from META-INF/spring.factories. There is more than one way to register additional ones:
Programmatically per application by calling the addListeners and addInitializers methods on SpringApplication before you run it.
Declaratively per application by setting context.initializer.classes or context.listener.classes.
Declaratively for all applications by adding a META-INF/spring.factories and packaging a jar file that the applications all use as a library.
The SpringApplication sends some special ApplicationEvents to the listeners (even some before the context is created), and then registers the listeners for events published by the ApplicationContext as well. See Section 23.4, “Application events and listeners” in the ‘Spring Boot features’ section for a complete list.
It is also possible to customize the Environment before the application context is refreshed using EnvironmentPostProcessor. Each implementation should be registered in META-INF/spring.factories:
org.springframework.boot.env.EnvironmentPostProcessor=com.example.YourEnvironmentPostProcessor
My way was always to add an ApplicationEnvironmentPreparedEvent listener:
public class IntegrationTestBootstrapApplicationListener implements
ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 4;
public static final String PROPERTY_SOURCE_NAME = "integrationTestProps";
private int order = DEFAULT_ORDER;
public void setOrder(int order) {
this.order = order;
}
#Override
public int getOrder() {
return this.order;
}
#Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
if (!environment.getPropertySources().contains(PROPERTY_SOURCE_NAME)) {
Map<String, Object> properties = ...; // generate the values
// use whatever precedence you want - first, last, before, after
environment.getPropertySources().addLast(
new MapPropertySource(PROPERTY_SOURCE_NAME, properties));
}
}
}
But you can just as easily use an initializer:
public class IntegrationTestBootstrapApplicationListener implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
private static final String PROPERTY_SOURCE_NAME = "integrationTestProps";
#Override
public void initialize(final ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
Map<String, Object> properties = ...; // generate the values
// use whatever precedence you want - first, last, before, after
environment.getPropertySources().addLast(
new MapPropertySource(PROPERTY_SOURCE_NAME, properties));
}
}
Based on parameters passed to a method, I need to select from one of many Spring beans that are implementations of the same class, but configured with different parameters.
E.g. if user A invokes the method, I need to call dooFoo() on bean A, but if it's user B then I need to call the very same method, only on bean B.
Is there a 'Springier' way of doing this other than sticking all the beans in a map, and deriving a key from the parameters passed to my method?
We face that issue in our project, and we solve it through a Factory-Like class. The client class -the one that needed the bean at runtime- had an instance of the factory, that was injected through Spring:
#Component
public class ImTheClient{
#Autowired
private ImTheFactory factory;
public void doSomething(
Parameters parameters) throws Exception{
IWantThis theInstance = factory.getInstance(parameters);
}
}
So, the IWantThis instance depends on the runtime value of the parameters parameter. The Factory implementation goes like this:
#Component
public class ImTheFactoryImpl implements
ImTheFactory {
#Autowired
private IWantThisBadly anInstance;
#Autowired
private IAlsoWantThis anotherInstance;
#Override
public IWantThis getInstance(Parameters parameters) {
if (parameters.equals(Parameters.THIS)) {
return anInstance;
}
if (parameters.equals(Parameters.THAT)) {
return anotherInstance;
}
return null;
}
}
So, the factory instance holds reference to both of the posible values of the IWantThis class, being IWantThisBadly and IAlsoWantThis both implementations of IWantThis.
Seems like do you want a ServiceLocator using the application context as registry.
See ServiceLocatorFactoryBean support class for creating ServiceLocators mapping keys to bean names without coupling client code to Spring.
Other option is to use a naming convention or annotation based configuration.
for example, assuming that you annotate Services with #ExampleAnnotation("someId"), you can use something like the following Service Locator to retrieve them.
public class AnnotationServiceLocator implements ServiceLocator {
#Autowired
private ApplicationContext context;
private Map<String, Service> services;
public Service getService(String id) {
checkServices();
return services.get(id);
}
private void checkServices() {
if (services == null) {
services = new HashMap<String, Service>();
Map<String, Object> beans = context.getBeansWithAnnotation(ExampleAnnotation.class);
for (Object bean : beans.values()) {
ExampleAnnotation ann = bean.getClass().getAnnotation(ExampleAnnotation.class);
services.put(ann.value(), (Service) bean);
}
}
}
}
Sticking them in a map sounds fine. If it's a Spring-managed map (using util:map, or in Java config), that's better than creating it somewhere else, because then Spring owns all the object references and can manage their lifecycle properly.
If the beans (A, B) you are talking about are SessionScope its no problem at all, they will be selected correctly.
public class BusinessLogic {
private BaseClassOfBeanAandB bean;
public void methodCalledByUserAorB() {
bean.doFoo();
}
}
I have a class called Drives which dynamically instantiates FsAccess beans.
I have a service class called ServersAccessService which finds FsAccess beans and stores them in a map using #autowired. Is there a way to have the service class initiate the #autowired after Drives is finished instantiating the FsAccess beans?
Service Class:
public class ServersAccessService implements DisposableBean {
protected static final Log log = LogFactory.getLog(ServersAccessService.class);
protected static Map<String, FsAccess> servers = new HashMap<String, FsAccess>();
I
protected Map<String, FsAccess> restrictedServers = new HashMap<String, FsAccess>();
protected boolean isInitialized = false;
protected static Map<String, DrivesCategory> drivesCategories = new HashMap<String, DrivesCategory>();
#Autowired
public void setServers(List<FsAccess> servers) {
for(FsAccess server: servers) {
this.servers.put(server.getDriveName(), server);
}
}
Drives class:
MyBeanFactory mbf = new MyBeanFactory();
//loop through each drive in driveList
for(String name:driveList)
{
String fullUri = "smb://naz-fs3/home/"+name;
String icon = "/esup-portlet-stockage/img/drives/root.png";
VfsAccessImpl drive = mbf.createInstance();
//Set attribute information
drive.setDriveName(name);
drive.setIcon(icon);
drive.setUri(fullUri);
drive.setContextToken(name);
}
If the Drives bean is instantiating the FsAccess beans in it's initialization phase, you may declare the dependency of the ServersAccessService bean via depends-on to the Drives bean. This forces the initialization of the Drives bean before the initialization of the ServersAccessService bean.
I believe what you want is to make your drives class a #Configuration bean, and make the method that returns a List annotated with #Bean. That way the spring container knows about the FsAccess list and it can be available for injection via #Autowired.
EDIT:
Reference: http://blog.springsource.com/2006/11/28/a-java-configuration-option-for-spring/