/*
* Copyright (C) 2011 Laurent Caillette
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.novelang.outfit.shell;
import java.io.IOException;
import java.util.Map;
import javax.management.InstanceNotFoundException;
import javax.management.JMX;
import javax.management.MBeanRegistrationException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import com.google.common.collect.Maps;
import org.apache.commons.lang.StringUtils;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* For a given host and port, takes care of every JMX connections and beans and maps them to {@link JmxKit} instances.
* <p>
* Inside the Novelang project, {@link org.novelang.outfit.shell.JavaShell} doesn't need more that one {@link JmxKit}
* instance. But one (commercial) project the author is working on needs this kind of mapping.
* <p>
* This implementation is not thread-safe.
*
* @author Laurent Caillette
*/
public class JmxBeanPool {
private final String host ;
private final int port ;
public JmxBeanPool( final String host, final int port ) {
checkArgument( ! StringUtils.isBlank( host ) ) ;
checkArgument( port > 0 ) ;
this.host = host ;
this.port = port ;
}
private final Map< JmxKit, JmxConnectionBundle> connectionBundles = Maps.newHashMap() ;
/**
* This is a {@code Map} between a ({@code ObjectName}, {@link org.novelang.outfit.shell.JmxKit}) pair,
* and a ({@link ObjectName}, JMX bean proxy, and {@link JmxConnectionBundle}) triplet.
*/
private final Map<JmxBeanKey, JmxBeanValue> managedBeans = Maps.newHashMap() ;
private JmxConnectionBundle getOrCreateConnectionBundle( final JmxKit someJmxKit )
throws IOException, InterruptedException
{
final JmxConnectionBundle connectionBundle ;
final JmxConnectionBundle maybeConnectionBundle = connectionBundles.get( someJmxKit ) ;
if( maybeConnectionBundle == null ) {
final JMXConnector jmxConnector = someJmxKit.createJmxConnector( host, port ) ;
connectionBundle = new JmxConnectionBundle( jmxConnector, jmxConnector.getMBeanServerConnection() ) ;
connectionBundles.put( someJmxKit, connectionBundle ) ;
} else {
connectionBundle = maybeConnectionBundle ;
}
return connectionBundle ;
}
public < BEAN > BEAN getManagedBean(
final Class< BEAN > beanClass,
final ObjectName beanName,
final JmxKit jmxKit
) throws IOException, InterruptedException
{
checkNotNull( beanClass ) ;
checkNotNull( beanName ) ;
checkNotNull( jmxKit ) ;
final JmxBeanKey key = new JmxBeanKey( beanName, jmxKit ) ;
final JmxBeanValue value = managedBeans.get( key ) ;
final BEAN bean ;
if( value == null ) {
final JmxConnectionBundle connectionBundle = getOrCreateConnectionBundle( jmxKit ) ;
bean = JMX.newMBeanProxy( connectionBundle.connection, beanName, beanClass, true ) ;
final JmxBeanValue newValue = new JmxBeanValue(
connectionBundle,
beanName,
bean
) ;
managedBeans.put( key, newValue ) ;
} else {
//noinspection unchecked
bean = ( BEAN ) value.getJmxBean() ;
}
return bean ;
}
/**
* Unregisters JMX Beans and closes the {@link javax.management.remote.JMXConnector}s.
* This method should close the default JMX connector o {@link org.novelang.outfit.shell.JavaShell} because, when there is one, there is always
* a registered {@link org.novelang.outfit.shell.insider.Insider} at startup so it should appear inside the
* {@code Map}.
*
*/
public void disconnectAll() {
// First, unregister all beans.
for( final JmxBeanValue value : managedBeans.values() ) {
try {
value.getConnectionBundle().connection.unregisterMBean( value.getObjectName() ) ;
} catch( InstanceNotFoundException e ) {
logCouldntUnregister( value.getObjectName(), e ) ;
} catch( MBeanRegistrationException e ) {
logCouldntUnregister( value.getObjectName(), e ) ;
} catch( IOException e ) {
logCouldntUnregister( value.getObjectName(), e ) ;
}
}
// Now we can close safely. The JMXConnector#close() method has no effect when called more than once.
for( final JmxBeanValue value : managedBeans.values() ) {
try {
value.getConnectionBundle().connector.close() ;
} catch( IOException e ) {
logCouldntUnregister( value.getConnectionBundle().connector, e ) ;
}
}
}
protected void logCouldntUnregister( final Object culprit, final Exception e ) {
/*
LOG.debug( "Couldn't disconnect or unregister " + culprit + ", cause: " + e.getClass() +
" (may be normal if other VM terminated)." ) ;
*/
}
}