How to dynamically load OSGI fragment programmatically from within Java? - java

How can I load an OSGI fragment, along with all of the resources within its bundle (config files, etc), programmatically from Java?
I would like to access the resources via getResource() as if they were in the internal Jar.

First, you'll need a handle to bundle context. From thereon you can listen to bundle references, there get a Bundle instance and use its classloader to do what you want.
Example that uses a Servlet to hook into bundle context and ServiceTracker to listen for osgi bundles appearing
public class AnotherHelloWorldServlet extends HttpServlet {
private MessageService service = null;
private BundleContext context;
#Override
public void init(final ServletConfig config) throws ServletException {
super.init(config);
context = (BundleContext) config.getServletContext().getAttribute("osgi-bundlecontext");
final AnotherHelloWorldServlet servlet = this;
ServiceTracker tracker = new ServiceTracker(context,
MessageService.class.getName(), null) {
#Override
public Object addingService(final ServiceReference sref) {
log.infof("Adding service: %s to %s", sref, servlet);
service = (MessageService) super.addingService(sref);
return service;
}
#Override
public void removedService(final ServiceReference sref, final Object sinst) {
super.removedService(sref, service);
log.infof("Removing service: %s from %s", sref, servlet);
service = null;
}
};
tracker.open();
}
}
with a ServiceReference you can call getBundle() to get a bundle instance, and for that, getResource() to get what you asked for.
If you cannot use a servlet or similar (=if you are outside Java EE/servlet container), there aren't that many standard ways to hook into the bundle context that I know of. In that case you might need to resort to BundleContextProvider hack to get it.

Related

Spring Boot customize server.tomcat.threads.min-spare for management server only

I have an application with management.server enabled:
management.server.port=8081
When I start application, I have:
10 threads for 8080 HTTP nio connector
10 threads for 8081 HTTP nio connector
But I would like to reduce min-spare only for management (8081) and not for the web application (8080)
Looking at Spring code, it seems it's not possible, can someone confirm ?
EDIT: The approach below is not sufficient as the ManagementWebServerFactoryCustomizer is also a ConfigurableWebServerFactory and will thus be applied to the main server.
Adding logic to check againgst the management port is not helping as the management context has its very own wiring and won't pick up the bean.
Looks like it's not possible to hook into the management server configuration easily (would be easier if ServletManagementContextFactory were public).
You can look into ServletManagementChildContextConfiguration to see how the management server is wired.
You could hook into the management server configuration by providing a ManagementWebServerFactoryCustomizer like this (not sure if there's an easier way):
#Configuration
public class TomcatManagementCustomizerConfiguration {
#Bean
ManagementWebServerFactoryCustomizer<ConfigurableServletWebServerFactory> servletManagementWebServerFactoryCustomizer(
#Value("${management.server.threads.min-spare:5}") int managementMinSpareThreads,
ListableBeanFactory beanFactory) {
return new TomcatManagementCustomizer(beanFactory, managementMinSpareThreads);
}
static class TomcatManagementCustomizer extends ManagementWebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
private final int managementMinSpareThreads;
protected TomcatManagementCustomizer(ListableBeanFactory beanFactory, int managementMinSpareThreads) {
super(beanFactory, TomcatWebServerFactoryCustomizer.class);
this.managementMinSpareThreads = managementMinSpareThreads;
}
#Override
#SuppressWarnings("rawtypes")
protected void customize(ConfigurableServletWebServerFactory factory, ManagementServerProperties managementServerProperties, ServerProperties serverProperties) {
super.customize(factory, managementServerProperties, serverProperties);
((TomcatServletWebServerFactory) factory).addConnectorCustomizers((connector) -> {
ProtocolHandler handler = connector.getProtocolHandler();
if (handler instanceof AbstractProtocol) {
AbstractProtocol protocol = (AbstractProtocol) handler;
protocol.setMinSpareThreads(managementMinSpareThreads);
}
});
}
}
}
Can you not just put the following in either properties file or YAML file?
Or is there something I misunderstood?
server.tomcat.threads.min-spare=2
(This is for properties file)
Just to verify (You don't need this as you have been checking the updated value in the log)
Put the following in either properties file or YAML file
management.endpoints.web.exposure.include=health,info,metrics,env
(This is for properties file)
And visit /actuator/env/server.tomcat.threads.min-spare
You need actuator dependency for the link above to work.
You can use #ManagementConfigurationContext and add the configuration class to to your META-INF/spring.properties file.
It is also important to place the configuration class in a package which is not the main package or sub-package of your main application context. This is so that this configuration only applies to the management context.
Below is the sampel configuration following #Holgzn's response.
#ManagementContextConfiguration
public class TomcatManagementCustomizerConfiguration {
#Bean
ManagementWebServerFactoryCustomizer<ConfigurableServletWebServerFactory> servletManagementWebServerFactoryCustomizer(
#Value("${management.server.threads.min-spare:5}") int managementMinSpareThreads,
ListableBeanFactory beanFactory) {
return new TomcatManagementCustomizer(beanFactory, managementMinSpareThreads);
}
static class TomcatManagementCustomizer extends ManagementWebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
private final int managementMinSpareThreads;
protected TomcatManagementCustomizer(ListableBeanFactory beanFactory, int managementMinSpareThreads) {
super(beanFactory, TomcatWebServerFactoryCustomizer.class);
this.managementMinSpareThreads = managementMinSpareThreads;
}
#Override
#SuppressWarnings("rawtypes")
protected void customize(ConfigurableServletWebServerFactory factory, ManagementServerProperties managementServerProperties, ServerProperties serverProperties) {
super.customize(factory, managementServerProperties, serverProperties);
((TomcatServletWebServerFactory) factory).addConnectorCustomizers((connector) -> {
ProtocolHandler handler = connector.getProtocolHandler();
if (handler instanceof AbstractProtocol) {
AbstractProtocol protocol = (AbstractProtocol) handler;
protocol.setMinSpareThreads(managementMinSpareThreads);
}
});
}
}
}
The spring.properties file
org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration=<package>.TomcatManagementCustomizerConfiguration

SpringBoot Tomcat Embedded Global JNDI Resource

I know there are a lot of questions about JNDI Resources in tomcat embedded, but I tried all the solutions I found without success.
I have an application that expose Rest API for my clients. Inside this app, we have a async solution using JMS and Amazon SQS. The App uses third-part libs, that uses JNDI to get sql.Datasource, because of that, I need use JNDI Datasource.
The problem is, when the App does a call to this libs in the same thread of Rest Controller, the JNDI Lookup works, and the datasource is getted.
When my #JmsListener calls this libs, I get a NamingNotFoungException.
I've used context.list("java") in 2 points of my code and confirmed that, inside JmsListener, there is no JNDI Context.
My tomcat factory class:
Configuration
public class CustomTomcatEmbeddedServletContainerFactory {
#Value("${spring.log.datasource.jndiName}")
private String logJndiName;
#Value("${spring.log.datasource.password}")
private String logPassword;
#Value("${spring.log.datasource.url}")
private String logUrl;
#Value("${spring.log.datasource.username}")
private String logUsername;
#Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
return new TomcatEmbeddedServletContainerFactory() {
#Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatEmbeddedServletContainer(tomcat);
}
#Override
protected void postProcessContext(Context context) {
// LogDS
context.getNamingResources()
.addResource(
getContextResource(logJndiName, logUrl, logUsername, logPassword)
);
ContextResourceLink contextResourceLink = new
ContextResourceLink();
contextResourceLink.setGlobal(logJndiName);
contextResourceLink.setName(logJndiName);
contextResourceLink.setType("javax.sql.DataSource");
context.getNamingResources().addResourceLink(contextResourceLink);
}
private ContextResource getContextResource(
final String name
, final String url
, final String username
, final String password
) {
ContextResource resource = new ContextResource();
resource.setName(name);
resource.setType(DataSource.class.getName());
resource.setProperty("factory", "com.zaxxer.hikari.HikariJNDIFactory");
resource.setProperty("jdbcUrl", url);
resource.setProperty("dataSource.user", username);
resource.setProperty("dataSource.password", AESCrypto.decrypt(password));
resource.setScope("Sharable");
return resource;
}
};
}
}
Any idea of this problem?
-------Update---------
When I use the code below, the context in JMSListener solve but my RestController doesn't answer anymore, a 404 http status happen.
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {
tomcat.enableNaming();
TomcatEmbeddedServletContainer container = super.getTomcatEmbeddedServletContainer(tomcat);
for (Container child : container.getTomcat().getHost().findChildren()) {
if (child instanceof Context) {
ClassLoader contextClassLoader = ((Context) child).getLoader().getClassLoader();
Thread.currentThread().setContextClassLoader(contextClassLoader);
break;
}
}
return container;
}
-------Update2---------
My problem is fixed. Instead of returning "container", like I said above, I was returning super.getTomcatEmbeddedServletContainer(tomcat); The manual GlobalContext in my first update works well!
My problem is fixed. Instead of returning "container", like I said above, I was returning super.getTomcatEmbeddedServletContainer(tomcat); The manual GlobalContext in my first update works well!

Jersey Endpoint+OSGi Dependency, Keeping Track

I have a Jersey endpoint which uses a custom OSGi Service ExceptionManager Service.
#Path("service")
public class ServiceFacade {
private volatile ExceptionManager exceptionManager;
public ServiceFacade() {
BundleContext bC = FrameworkUtil.getBundle(ServiceFacade.class).getBundleContext();
ServiceReference<ExceptionManager> sR = bC.getServiceReference(ExceptionManager.class);
if (sR != null)
this.exceptionManager = bC.getService(sR);
}
#GET
#Consumes({MediaType.APPLICATION_JSON})
#Produces(MediaType.APPLICATION_JSON)
public Response sayHello() {
try {
if (exceptionManager == null)
return Response.status(Status.SERVICE_UNAVAILABLE).build();
// Do some work...
} catch (Exception e) {
exceptionManager.handle(e);
}
}
}
This Jersey class is added to the Jersey Application as a simple class, that means that every time a user hits this endpoint, a new instance of this class is created to handle the request. As you can see, the class contains a constructor which initializes the ExceptionManager Service. My question is, isn't there a simplified way of retrieving the service without going to BundleContext?
I have seen DependencyManager, but this bundle seems to only add the dependencies to the class (ServiceFacade in this case) during the Activation process, but that dependency resolution is too early this has to be done during run-time, every time an instance is created. Bellow is an approximation with DependencyManager but is not a solution for this:
public class Activator extends DependencyActivatorBase {
#Override
public void init(BundleContext bundleContext, DependencyManager dependencyManager) throws Exception {
dependencyManager.add(createComponent()
.setImplementation(ServiceFacade.class)
.add(createServiceDependency()
.setService(ExceptionManager.class)
.setRequired(true));
}
}
Thanks.-
You can obtain the reference to an OSGi service without accessing to BundleContext by using Declarative Services. A tutorial can be found here.
You can make the endpoint a singleton resource. This way you can let the dependency manager create a single instance and inject services and then add that instance to the Jersey application.
There are a few limitations, like Jersey's field or constructor injection does not work. You also have to be careful about concurrency when using fields of the resource.

Atmosphere framework, BroadcasterFactory.getDefault() alternative

I am using Atmosphere framework and it looks in the newest version(s) of the library the method:
BroadcasterFactory.getDefault() is depricated. (and this method was essentially used everywhere and I cannot find an example how to use the new "way" )
The javadoc states :
#deprecated Use {#link org.atmosphere.cpr.AtmosphereConfig#resourcesFactory()}
However I cannot find a single documentation how to get the AtmosphereConfig to be able to get the resourceFactory (which is an instance method).
Can someone tell me how to get the config .. or the AtmosphereFramework object itself from which I can get the config or any example which is up2date ?
Not sure if it works, but try to get ServletContext and use getAttribute(AtmosphereFramework.class.getName()) to obtain AtmosphereFramework. If you are using Spring, try to autowire AtmosphereFramework directly.
You can also get BroadcasterFactory from AtmosphereResource and then lookup for Broadcaster like:
private String path;
private BroadcasterFactory broadcasterFactory;
#Ready(value = Ready.DELIVER_TO.ALL)
public void onReady(final AtmosphereResource r) {
System.out.println("onConnect");
r.addEventListener(new AtmosphereConnectionController());
if(broadcasterFactory == null){
path = r.getBroadcaster().getID();
broadcasterFactory = r.getAtmosphereConfig().getBroadcasterFactory();
}
}
//later in code
broadcasterFactory.lookup(path).broadcast("message");
Use dependency injection. In my project, it goes like this:
#MeteorService(path = "/recursos/fila-de-atendimento", interceptors = {AtmosphereResourceLifecycleInterceptor.class})
public class FilaDeAtendimentoResource extends HttpServlet {
#Inject
private BroadcasterFactory broadcasterFactory;
...
/** Used for registering for a message */
public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
...
Broadcaster broadcaster = broadcasterFactory.lookup(broadcasterId, true);
meteor.setBroadcaster(broadcaster);
...
}
}

How do you properly implement a ManagedServiceFactory as Decalarative Service in OSGi?

I have services that need to be created on a per-configuration base, each of which relies on an external resource and thus should manage it's own lifcycle (i.e. (de)register the service). Thus implementing these as DS and let SCR spawn multiple instances does not work.
One can implement a bundle that registers a ManagedServiceFactory to accomplish this task perfectly (see my previous post). But as a consequence, if the factory depends on several other services, you need to start tracking those services and write a lot of glue code to get everything running. Instead I'd like to implement the factory as (singleton) declarative service, for which the SCR registers a ManagedServiceFactory at the Service Registry.
Here's my attempt:
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedServiceFactory;
import org.osgi.service.component.ComponentContext;
#SuppressWarnings({ "rawtypes", "unchecked" })
public class Factory implements ManagedServiceFactory {
private BundleContext bundleCtxt;
private Map<String, ServiceRegistration> services;
public void activate(ComponentContext context) throws Exception {
System.out.println("actiavting...");
this.bundleCtxt = context.getBundleContext();
services = new HashMap<String, ServiceRegistration>();
}
public void deactivate(ComponentContext context) {
for(ServiceRegistration reg : services.values()) {
System.out.println("deregister " + reg);
reg.unregister();
}
services.clear();
}
#Override
public String getName() {
System.out.println("returning factory name");
return "my.project.servicefactory";
}
#Override
public void updated(String pid, Dictionary properties)
throws ConfigurationException {
System.out.println("retrieved update for pid " + pid);
ServiceRegistration reg = services.get(pid);
if (reg == null) {
services.put(pid, bundleCtxt.registerService(ServiceInterface.class,
new Service(), properties));
} else {
// i should to some update here
}
}
#Override
public void deleted(String pid) {
ServiceRegistration reg = services.get(pid);
if (reg != null) {
reg.unregister();
services.remove(pid);
}
}
}
and the service description:
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" configuration-policy="ignore" name="my.project.servicefactory">
<implementation class="my.project.factory.Factory"/>
<service>
<provide interface="org.osgi.service.cm.ManagedServiceFactory"/>
</service>
<property name="service.pid" type="String" value="my.project.servicefactory"/>
</scr:component>
I already found out that the "factory" property in the service description is the wrong path, because this way the component is never registerd as ManagedServiceFactory in the Service Registry, instead it becomes a ComponentFactory.
As a kind of hack, I just added a component property, namely
<property name="service.pid" type="String" value="my.project.servicefactory"/>
and added configuration-policy="ignore". This works: configurations named my.project.servicefactory-foobar.cfg are handed to my service, which registers them as in the Service Registry, everything fine.
But theres two things about it i do not like:
manually setting the property service.pid feels like a dirty hack to me
setting configuration-policy="ignore" prevents me from configuring the ManagedServiceFactory itsself. If I escape this property or set it to require, I would get one ManagedServiceFactory for a configuration named my.project.servicefactory.cfg and then two services for each configuration named with the pattern my.project.servicefactory-foobar.cfg: one ManagedServiceFactory that the SCR spawns and one ServiceInterface that my first ManagedServiceFactory registers when it gets notified about this new configuration. (At least this is not growing exponetially because SCR overwrites the service.pid property for factory configurations)
So how should I set this up properly?
PS: For those wondering about my reference to configurations on their filenames: I use Felix Fileinstall for the configurations, thus foo.cfg is put to the ConfigAdmin for PID foo, and foo-bar.cfg is put there for factory-pid foo.
Just use your DS instance headless, the properties, and register the service yourself:
#Component(immedate=true, provide={}, serviceFactory=true, configurationPolicy=require)
public class Mine {
BundleContext context;
volatile ServiceRegistration r;
#Activate
void activate(BundleContext context, Map<String,Object> map) {
this.context = context;
track(map);
}
#Deactivate
void deactivate() {
if ( r != null)
r.unregisterService();
}
void track(Map<String,Object> map) {
... // do your remote stuff
r = context.registerService(...);
...
}
}
Why doesn't the support in DS for this not work for you? See 112.6:
Factory Configuration – If a factory PID exists, with zero or more Configurations, that is equal to the configuration PID, then for each Configuration, a component configuration must be created that will obtain additional component properties from Configuration Admin.
This says that if the configuration pid of your component is the same as the factory pid in CM, then DS will create an instance of your component for each configuration under the factory pid.

Categories