How to register logback JMXConfigurator to WebSphere custom MBeanServer? - websphere

I tried to put <jmxConfigurator/> in logback configuration file. I am able to connect jconsole to local JVM running unit tests and interact with the logback mbean. However, when I deploy my web application to a remote Websphere application server and connect jconsole to that remote JVM, I can't see the logback mbean in the MBeans panel.
As a comparison, the web application is built with spring boot, which also register some MBeans by default. I can see MBeans of spring boot in both scenarios.
I investigated a bit further and found out logback always get MBeanServer instance from ManagementFactory.getPlatformMBeanServer(), while spring uses different approaches in Websphere/Weblogic environment.
It appears that in Websphere environment, the MBeanServer instance exposed for remote connection is NOT the default PlatformMBeanServer.
So the question is, how can I register the logback mbean to the WebSphere custom MBeanServer, rather than the default PlatformMBeanServer?
WebSphere custom MBeanServer is favoured because it is better integrated with security and clustering capabilities.

This is my workaround by extending JMXConfigurator.
Just for the record, there is no document endorsing such extension, and I didn't test it with multiple web-applications.
This class inherited most behaviors from JMXConfigurator, but it will register to / unregister from the MBeanServer that is injected by Spring.
#ManagedResource(objectName = AnnotatedJMXConfigurator.NAME, description = "Logback Configuration Management Bean")
#Component
public class AnnotatedJMXConfigurator extends JMXConfigurator {
public static final String NAME = "xxx.ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator";
private static final LoggerContext CONTEXT = (LoggerContext) LoggerFactory.getILoggerFactory();
private static final ObjectName OBJECT_NAME;
static {
try {
OBJECT_NAME = new ObjectName(NAME);
} catch (MalformedObjectNameException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
#Autowired
public AnnotatedJMXConfigurator(MBeanServer mbs) {
super(CONTEXT, mbs, OBJECT_NAME);
}
}

Related

How to view autoconfigure log output during spring boot server start

How to view autoconfigure log output during spring boot server start
I have created a spring boot application. It uses a shared library (Spring boot jar via maven dependency). Shared library class is loaded via
META-INF/spring.factories
I have mentioned the classes from the library in spring.factories. The job of shared library is to read Vault role id and Vault
secret id value from application.properties and call a REST API and fetch secrets from Vault. After fetching the secret it sets the value again in system property.
for (Map.Entry<String, String> entry : allSecrets.entrySet())
{
System.setProperty(entry.getKey(), entry.getValue());
}
Everything is working as expected. But I am not able to see logs from shared library in my logs.
shared library's package structure is com.myorg.abc. My spring boot package structure is com.myorg.xyz
I tried the following in application properties.
logging.level.root= DEBUG
logging.level.com.myorg.xyz: DEBUG
logging.level.com.myorg.abc: DEBUG
logging.level.org.springframework.boot.autoconfigure.logging=DEBUG
I am able to get logs only from my application but not from shared library. But when I change the shared library Logger.error to System.out, then I am getting the message in my application. How to view shared library's log in my application.
Spring boot initializes logging at least 3 times. The first happens when SpringApplication is loaded. It creates an SLF4J Logger before anything in Spring is accessed. This causes whatever logging implementation you have chosen to initialize. By default, it will use the logging configuration in the Spring jar. With Log4j 2 you can override this by setting log4j.configurationFile to the location of your desired configuration either as a system property or in a log4j.component.properties file.
Everything Spring does will be logged using this configuration until it initializes the logging configuration again, which is controlled by bootstrap.yml. Finally, your application's logging configuration is initialized which is configured either from application.yml or again from bootstrap.yml.
I replaced org.springframework.boot.env.EnvironmentPostProcessor with org.springframework.context.ApplicationListener in Spring.factories and it fixed the issue. I was able to get logs from shared library in invoking application.
Spring.factories
org.springframework.context.ApplicationListener=com.mypackage.MyClassName
MyClassName.java
public class MyClassName implements ApplicationListener<ApplicationPreparedEvent>
{
private static final Logger LOGGER = LoggerFactory.getLogger(MyClassName.class);
#Override
public void onApplicationEvent(ApplicationPreparedEvent applicationPreparedEvent)
{
ConfigurableEnvironment configurableEnvironment = applicationPreparedEvent.getApplicationContext()
.getEnvironment();
String roleId = configurableEnvironment.getProperty(Constants.VAULT_ROLE_ID_LITERAL);
String secretId = configurableEnvironment.getProperty(Constants.VAULT_SECRET_ID_LITERAL);
...
Optional<String> errorMessage = ServiceUtil.validateSystemProperty(roleId, secretId);
if (!errorMessage.isPresent())
{
Map<String, String> secret = ServiceUtil.getSecret(roleId, secretId);
for (Map.Entry<String, String> entry : secret.entrySet())
{
System.setProperty(entry.getKey(), entry.getValue());
}
LOGGER.info("Successfully populated secrets from Vault in system property");
}
else
{
LOGGER.error("Failed to populate secrets from Vault in system property. Error:{}", errorMessage.get());
}
}
}
application.properties
logging.level.com.myorg.abc: DEBUG

Is it possible to load properties from a web service during spring boot application startup?

I am building a new spring boot application deployable to bluemix (cloud foundry) which needs to do the following:
use spring-cloud-cloudfoundry-connector to discover user-provided "properties service": read the service URL and credentials from VCAP_APPLICATION env variable.
This step is completed.
connect to properties service via HTTP call, receive JSON response, parse individual property values and expose them as application properties (in Environment object?)
What would be the correct solution for this in spring-boot app?
In older non-boot Spring app, the property service call would be initiated early in Spring lifecycle by a class that extended PropertySourcesPlaceholderConfigurer and the property collection from the service would be handled inside postProcessBeanFactory() method call of the same class.
public class CustomPopertiesFactory
extends PropertySourcesPlaceholderConfigurer
implements EnvironmentAware {
private Properties properties;
getServiceCredentials() {
// parse VCAP_APPLICATION json
final String localVcapServices = System.getProperty("VCAP_SERVICES");
// extract url, username, pwd to connect to the service
}
connectToService () {
// via HTTP request using RestTemplate
// parse JSON response and add properties to this.properties
... this.properties.put("prop1", valueFromJson);
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
getServiceCredentials();
connectToService();
// load values from properties service into app properties
setProperties(properties);
// continue with lifecycle and load properties from other sources
super.postProcessBeanFactory(beanFactory);
}
}
That was painful to maintain and switch between cloud and local spring profiles IMO and I am wondering if spring boot has a better way of handling external properties.
I ended up replacing "properties service" with spring-cloud-config-server
and using spring-cloud-config-client
in my spring boot application to consume properties from spring-cloud-config-server.

SpringBoot and setting system properties

I develop SpringBoot app that conntects to Oracle Coherence cluster. App, as coherence node need some JVM properties to connect to cluster. I wanted to set this properties (taken from properties file) in spring boot custom starter. I set system properties in #Configuration class and I can read those without problem but coherence doesn't see one property tangosol.pof.enabled and it fails. When I call System.getProperty(..) it is there but not working (property is not seen by coherence).
It works when I #Autowire configuration class in some other bean in my application or when I have this configuration class not in spring boot starter but in my application.
This is my code:
Configuration class in starter (it works)
#Configuration
#PropertySource("coherence-app.properties")
public class EnvironmentConfig {
public static final Logger LOGGER = LoggerFactory.getLogger(EnvironmentConfig.class);
public EnvironmentConfig(Environment environment, ConfigurableApplicationContext ctx){
Properties props = new Properties();
ConfigsHelper.TANGOSOL_COHERENCE_CONFIGS.stream()
.forEach(prop -> props.setProperty(prop, environment.getProperty(prop)));
if (!ConfigsHelper.setTangosolCoherenceProperties(props)){
LOGGER.error("Can't set coherence props");
System.exit(1);
}
}
}
Then when I try to connect to cluster:
CacheFactory.ensureCluster();
I have error:
2017-08-09 13:17:56.049/7.494 Oracle Coherence GE 12.2.1.0.2
(thread=Cluster, member=n/a): Failed to deserialize the config Message
received from member 1. This member is configured with the following
serializer: com.tangosol.io.DefaultSerializer
{loader=sun.misc.Launcher$AppClassLoader#18b4aac2}, which may be
incompatible with the serializer configured by the sender.
java.io.StreamCorruptedException: invalid type: 100 at
com.tangosol.util.ExternalizableHelper.readObjectInternal(ExternalizableHelper.java:2477)
at
com.tangosol.util.ExternalizableHelper.readObject(ExternalizableHelper.java:2464)
at
com.tangosol.io.DefaultSerializer.deserialize(DefaultSerializer.java:66)
at
com.tangosol.coherence.component.util.daemon.queueProcessor.Service.readObject(Service.CDB:1)
at
com.tangosol.coherence.component.net.Message.readObject(Message.CDB:1)
at
com.tangosol.coherence.component.util.daemon.queueProcessor.service.grid.ClusterService$ServiceJoining.read(ClusterService.CDB:14)
at
com.tangosol.coherence.component.util.daemon.queueProcessor.service.Grid.deserializeMessage(Grid.CDB:20)
at
com.tangosol.coherence.component.util.daemon.queueProcessor.service.Grid.onNotify(Grid.CDB:21)
at
com.tangosol.coherence.component.util.daemon.queueProcessor.service.grid.ClusterService.onNotify(ClusterService.CDB:3)
at com.tangosol.coherence.component.util.Daemon.run(Daemon.CDB:45)
at java.lang.Thread.run(Thread.java:748)
It is connected with tangosol.pof.enabled=false property.
Strange is that when I call
System.getProperty("tangosol.pof.enabled")
before ensureCluster it's true.
This code works properly when it's not in starter. Then configuration bean is initializing earlier and works.
Do you have any idea how to solve this problem.

Spring boot Artemis embedded broker behaviour

Morning all,
I've been struggling lately with the spring-boot-artemis-starter.
My understanding of its spring-boot support was the following:
set spring.artemis.mode=embedded and, like tomcat, spring-boot will instanciate a broker reachable through tcp (server mode). The following command should be successful: nc -zv localhost 61616
set spring.artmis.mode=native and spring-boot will only configure the jms template according to the spring.artemis.* properties (client mode).
The client mode works just fine with a standalone artemis server on my machine.
Unfortunatelly, I could never manage to reach the tcp port in server mode.
I would be grateful if somebody confirms my understanding of the embedded mode.
Thank you for tour help
After some digging I noted that the implementation provided out of the box by the spring-boot-starter-artemis uses org.apache.activemq.artemis.core.remoting.impl.invm.InVMAcceptorFactory acceptor. I'm wondering if that's not the root cause (again I'm by no means an expert).
But it appears that there is a way to customize artemis configuration.
Therefore I tried the following configuration without any luck:
#SpringBootApplication
public class MyBroker {
public static void main(String[] args) throws Exception {
SpringApplication.run(MyBroker.class, args);
}
#Autowired
private ArtemisProperties artemisProperties;
#Bean
public ArtemisConfigurationCustomizer artemisConfigurationCustomizer() {
return configuration -> {
try {
configuration.addAcceptorConfiguration("netty", "tcp://localhost:" + artemisProperties.getPort());
} catch (Exception e) {
throw new RuntimeException("Failed to add netty transport acceptor to artemis instance");
}
};
}
}
You just have to add a Connector and an Acceptor to your Artemis Configuration. With Spring Boot Artemis starter Spring creates a Configuration bean which will be used for EmbeddedJMS configuration. You can see this in ArtemisEmbeddedConfigurationFactory class where an InVMAcceptorFactory will be set for the configuration. You can edit this bean and change Artemis behaviour through custom ArtemisConfigurationCustomizer bean which will be sucked up by Spring autoconfig and be applied to the Configuration.
An example config class for your Spring Boot application:
import org.apache.activemq.artemis.api.core.TransportConfiguration;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory;
import org.springframework.boot.autoconfigure.jms.artemis.ArtemisConfigurationCustomizer;
import org.springframework.context.annotation.Configuration;
#Configuration
public class ArtemisConfig implements ArtemisConfigurationCustomizer {
#Override
public void customize(org.apache.activemq.artemis.core.config.Configuration configuration) {
configuration.addConnectorConfiguration("nettyConnector", new TransportConfiguration(NettyConnectorFactory.class.getName()));
configuration.addAcceptorConfiguration(new TransportConfiguration(NettyAcceptorFactory.class.getName()));
}
}
My coworker and I had the exact same problem as the documentation on this link (chapter Artemis Support) says nothing about adding an individual ArtemisConfigurationCustomizer - Which is sad because we realized that without this Customizer our Spring Boot App would start and act as if everything was okay but actually it wouldn't do anything.
We also realized that without the Customizer the application.properties file is not beeing loaded so no matter what host or port you mentioned there it would not count.
After adding the Customizer as stated by the two examples it worked without a problem.
Here some results that we figured out:
It only loaded the application.properties after configuring an ArtemisConfigurationCustomizer
You don't need the broker.xml anymore with an embedded spring boot artemis client
Many examples showing the use of Artemis use a "in-vm" protocol while we just wanted to use the netty tcp protocol so we needed to add it into the configuration
For me the most important parameter was pub-sub-domain as I was using topics and not queues. If you are using topics this parameter needs to be set to true or the JMSListener won't read the messages.
See this page: stackoverflow jmslistener-usage-for-publish-subscribe-topic
When using a #JmsListener it uses a DefaultMessageListenerContainer
which extends JmsDestinationAccessor which by default has the
pubSubDomain set to false. When this property is false it is
operating on a queue. If you want to use topics you have to set this
properties value to true.
In Application.properties:
spring.jms.pub-sub-domain=true
If anyone is interested in the full example I have uploaded it to my github:
https://github.com/CorDharel/SpringBootArtemisServerExample
The embedded mode starts the broker as part of your application. There is no network protocol available with such setup, only InVM calls are allowed. The auto-configuration exposes the necessary pieces you can tune though I am not sure you can actually have a TCP/IP channel with the embedded mode.

Using Spring to Access an EJB Across Clusters in WebSphere using Grails

I have spent the last few days attempting to integrate a Grails (version 1.3.2) application with an EJB 2.1 application that is deployed on WebSphere 6.1. Once our grails apps are in production, they will be deployed to WebSphere as well. The EJB 2.1 application is widely used across our company and, in anything except a local development environment, is deployed to its own cluster. The way we handle this in our existing Java EE applications (all of which are non-Spring, non-Grails) is to bind a CORBA CosNaming Naming Context within each of our other clusters that can then be used to obtain references to our shared EJB 2.1 application. So, up to this point, if one of our application needed to interact with this application, they would do so using an approach like this:
String cosNameBinding = "ejbApp.HighAvail.cluster";
InitialContext initial = new InitialContext();
Context fedContext = (javax.naming.Context) initialCtx.lookup(cosNameBinding);
Then do the normal EJB-style lookup/narrow/invoke using the federated/CosNaming context:
Object ejbHomeAsObject = fedContext.lookup(jndiNameOfService);
EJBHome home = (EJBHome) PortableRemoteObject.narrow(ejbHomeAsObject, homeClass);
Object service = invokeMethod(homeClass, home, "create");
As you can see, there is a level of indirection that occurs here in order to go from the InitialContext to the federated naming Context that can be used to interact with the shared EJB application.
Running locally, I have both the Grails application and the EJB application deployed to the same server (non network deployment WAS, same profile&node). I have Spring configured like so:
beans = {
ejbJndi(org.springframework.jndi.JndiTemplate) {
environment = ["java.naming.factory.initial" :
"com.ibm.websphere.naming.WsnInitialContextFactory"]
}
crewMemberService(org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean) {
jndiName="hotelService/ejb/HotelService"
businessInterface="com.company.appName.hotel.HotelService"
lookupHomeOnStartup="false"
cacheHome="false"
refreshHomeOnConnectFailure="true"
jndiTemplate = ref("ejbJndi")
}
}
And I can successfully inject ejb references into my Grails controllers and invoke them. However, WebSphere can only resolve the JNDI lookup because they are both deployed on the same server. When we move it to one of our development environments, we'll need jndi lookups for these services to go against the federated naming context.
So my questions are:
Is there a way to do this with the classes that are provided within Spring and if so could you give me an idea of how I would need up modify my Spring config to do so?
Given that there is no flexibility around how we deploy the other app or gain references to its services (we must use the federated context), should I consider extending JndiTemplate and do the necessary wiring myself?
If anyone has faced this situation I would be most appreciative for any insights you may be able to offer.
In case anyone has this same question down the road, I ended up implementing an extension to Spring's JndiTemplate and using that. Here is the code:
public class FederatedJndiTemplate extends org.springframework.jndi.JndiTemplate
{
protected static final String JNDI_CONTEXT_BINDING_NAME = "fed.context.jndiName";
/**
* Obtain a JNDI naming context for the specified federated naming context.
*
* #throws NamingException if no "fed.context.jndiName" has been specified in
* the environment properties for the jndiTemplate or the container throws a naming
* exception.
*/
#Override
protected Context createInitialContext() throws NamingException {
Properties props = super.getEnvironment();
if(!props.containsKey(JNDI_CONTEXT_BINDING_NAME)) {
throw new NamingException("You must specify the federated naming context JNDI binding name");
}
String jndiBinding = props.getProperty(JNDI_CONTEXT_BINDING_NAME);
InitialContext initCtx = new InitialContext();
Context fedCtx = (Context) initCtx.lookup(jndiBinding);
return fedCtx;
}
}
Then inside my resources.groovy, I just used this JndiTemplate:
ejbJndi(com.myCompany.spring.jndi.FederatedJndiTemplate) {
environment = [
"fed.context.jndiName":"myServices.HighAvail.Cluster"]
}
hotelService(org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean) {
jndiName="hotelService/ejb/HotelService"
businessInterface="com.mycompany.appName.hotel.HotelService"
homeInterface="com.mycompany.appName.hotel.HotelServiceHome"
lookupHomeOnStartup="false"
jndiTemplate = ref("ejbJndi")
}

Resources