blob: dd66ffa2ce9aa5dc57ceee451494a4750e3457ab [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.jmxconnect;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanServerConnection;
import javax.management.NotificationBroadcaster;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.remote.JMXConnectionNotification;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServerErrorException;
import javax.management.remote.JMXServiceURL;
import javax.security.auth.Subject;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.spring.remoting.CamelProxyFactoryBean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* <p>
* The client end of a JMX API connector. An object of this type can be used to establish a connection to a connector
* server.
* </p>
* <p/>
* <p>
* A newly-created object of this type is unconnected. Its {@link #connect()} method must be called before it can
* be used. However, objects created by {@link JMXConnectorFactory#connect(JMXServiceURL, Map)
* JMXConnectorFactory.connect} are already connected.
* </p>
*
* @version $Revision$
*/
public class CamelJmxConnector implements JMXConnector, CamelContextAware {
private static final Log LOG = LogFactory.getLog(JMXConnector.class);
private NotificationBroadcasterSupport connectionNotifier = new NotificationBroadcasterSupport();
private AtomicLong notificationNumber = new AtomicLong();
private Map env;
private String endpointUri;
private CamelProxyFactoryBean proxy;
private MBeanCamelServerConnectionClient client;
private boolean connected;
private CamelContext camelContext;
private String connectionId;
public CamelJmxConnector(Map env, String endpointUri) {
this.env = env;
this.endpointUri = endpointUri;
}
public CamelJmxConnector(Map env, JMXServiceURL url) throws IOException {
this(env, CamelJmxConnectorSupport.getEndpointUri(url, "camel"));
// set any props in the url
// TODO
// populateProperties(this, endpointUri);
}
/**
* <p>
* Establishes the connection to the connector server. This method is equivalent to {@link #connect(Map)
* connect(null)}.
* </p>
*
* @throws IOException if the connection could not be made because of a communication problem.
* @throws SecurityException if the connection could not be made for security reasons.
*/
public void connect() throws IOException {
connect(this.env);
}
/**
* <p>
* Establishes the connection to the connector server.
* </p>
* <p/>
* <p>
* If <code>connect</code> has already been called successfully on this object, calling it again has no effect.
* If, however, {@link #close} was called after <code>connect</code>, the new <code>connect</code> will throw
* an <code>IOException</code>.
* <p>
* <p/>
* <p>
* Otherwise, either <code>connect</code> has never been called on this object, or it has been called but produced
* an exception. Then calling <code>connect</code> will attempt to establish a connection to the connector server.
* </p>
*
* @param env the properties of the connection. Properties in this map override properties in the map specified when
* the <code>JMXConnector</code> was created, if any. This parameter can be null, which is equivalent
* to an empty map.
* @throws IOException if the connection could not be made because of a communication problem.
* @throws SecurityException if the connection could not be made for security reasons.
*/
public void connect(Map env) throws IOException {
if (!connected) {
try {
proxy = new CamelProxyFactoryBean();
proxy.setCamelContext(getCamelContext());
proxy.setServiceInterface(javax.management.MBeanServerConnection.class);
proxy.setServiceInterface(MBeanCamelServerConnection.class);
proxy.setServiceUrl(endpointUri);
proxy.afterPropertiesSet();
client = new MBeanCamelServerConnectionClient((MBeanCamelServerConnection) proxy.getObject() /* TODO */);
connectionId = client.generateId();
// TODO we need to establish a replyToEndpoint and inform the server!
sendConnectionNotificationOpened();
} catch (Exception e) {
LOG.error("Failed to connect: " + e, e);
IOException ioe = new IOException(e.getMessage());
throw ioe;
}
connected = true;
}
}
/**
* <p>
* Returns an <code>MBeanServerConnection</code> object representing a remote MBean server. For a given
* <code>JMXConnector</code>, two successful calls to this method will usually return the same
* <code>MBeanServerConnection</code> object, though this is not required.
* </p>
* <p/>
* <p>
* For each method in the returned <code>MBeanServerConnection</code>, calling the method causes the
* corresponding method to be called in the remote MBean server. The value returned by the MBean server method is
* the value returned to the client. If the MBean server method produces an <code>Exception</code>, the same
* <code>Exception</code> is seen by the client. If the MBean server method, or the attempt to call it, produces
* an <code>Error</code>, the <code>Error</code> is wrapped in a {@link JMXServerErrorException}, which is
* seen by the client.
* </p>
* <p/>
* <p>
* Calling this method is equivalent to calling
* {@link #getMBeanServerConnection(Subject) getMBeanServerConnection(null)} meaning that no delegation subject is
* specified and that all the operations called on the <code>MBeanServerConnection</code> must use the
* authenticated subject, if any.
* </p>
*
* @return an object that implements the <code>MBeanServerConnection</code> interface by forwarding its methods to
* the remote MBean server.
*/
public MBeanServerConnection getMBeanServerConnection() {
return client;
}
/**
* <p>
* Returns an <code>MBeanServerConnection</code> object representing a remote MBean server on which operations are
* performed on behalf of the supplied delegation subject. For a given <code>JMXConnector</code> and
* <code>Subject</code>, two successful calls to this method will usually return the same
* <code>MBeanServerConnection</code> object, though this is not required.
* </p>
* <p/>
* <p>
* For each method in the returned <code>MBeanServerConnection</code>, calling the method causes the
* corresponding method to be called in the remote MBean server on behalf of the given delegation subject instead of
* the authenticated subject. The value returned by the MBean server method is the value returned to the client. If
* the MBean server method produces an <code>Exception</code>, the same <code>Exception</code> is seen by the
* client. If the MBean server method, or the attempt to call it, produces an <code>Error</code>, the
* <code>Error</code> is wrapped in a {@link JMXServerErrorException}, which is seen by the client.
* </p>
*
* @param delegationSubject the <code>Subject</code> on behalf of which requests will be performed. Can be null, in which case
* requests will be performed on behalf of the authenticated Subject, if any.
* @return an object that implements the <code>MBeanServerConnection</code> interface by forwarding its methods to
* the remote MBean server on behalf of a given delegation subject.
*/
public MBeanServerConnection getMBeanServerConnection(Subject delegationSubject) {
throw new UnsupportedOperationException();
}
/**
* <p>
* Closes the client connection to its server. Any ongoing or new request using the MBeanServerConnection returned
* by {@link #getMBeanServerConnection()} will get an <code>IOException</code>.
* </p>
* <p/>
* <p>
* If <code>close</code> has already been called successfully on this object, calling it again has no effect. If
* <code>close</code> has never been called, or if it was called but produced an exception, an attempt will be
* made to close the connection. This attempt can succeed, in which case <code>close</code> will return normally,
* or it can generate an exception.
* </p>
* <p/>
* <p>
* Closing a connection is a potentially slow operation. For example, if the server has crashed, the close operation
* might have to wait for a network protocol timeout. Callers that do not want to block in a close operation should
* do it in a separate thread.
* </p>
*
* @throws IOException if the connection cannot be closed cleanly. If this exception is thrown, it is not known whether
* the server end of the connection has been cleanly closed.
*/
public void close() throws IOException {
if (connected) {
connected = false;
try {
sendConnectionNotificationClosed();
proxy.destroy();
} catch (Exception e) {
LOG.error("Failed to destroy proxy: " + e, e);
throw new IOException(e.getMessage());
}
}
}
/**
* <p>
* Adds a listener to be informed of changes in connection status. The listener will receive notifications of type
* {@link JMXConnectionNotification}. An implementation can send other types of notifications too.
* </p>
* <p/>
* <p>
* Any number of listeners can be added with this method. The same listener can be added more than once with the
* same or different values for the filter and handback. There is no special treatment of a duplicate entry. For
* example, if a listener is registered twice with no filter, then its <code>handleNotification</code> method will
* be called twice for each notification.
* </p>
*
* @param listener a listener to receive connection status notifications.
* @param filter a filter to select which notifications are to be delivered to the listener, or null if all
* notifications are to be delivered.
* @param handback an object to be given to the listener along with each notification. Can be null.
* @throws NullPointerException if <code>listener</code> is null.
* @see #removeConnectionNotificationListener
* @see NotificationBroadcaster#addNotificationListener
*/
public void addConnectionNotificationListener(NotificationListener listener, NotificationFilter filter,
Object handback) {
connectionNotifier.addNotificationListener(listener, filter, handback);
}
/**
* <p>
* Removes a listener from the list to be informed of changes in status. The listener must previously have been
* added. If there is more than one matching listener, all are removed.
* </p>
*
* @param listener a listener to receive connection status notifications.
* @throws NullPointerException if <code>listener</code> is null.
* @throws ListenerNotFoundException if the listener is not registered with this <code>JMXConnector</code>.
* @see #removeConnectionNotificationListener(NotificationListener, NotificationFilter, Object)
* @see #addConnectionNotificationListener
* @see NotificationEmitter#removeNotificationListener
*/
public void removeConnectionNotificationListener(NotificationListener listener) throws ListenerNotFoundException {
connectionNotifier.removeNotificationListener(listener);
}
/**
* <p>
* Removes a listener from the list to be informed of changes in status. The listener must previously have been
* added with the same three parameters. If there is more than one matching listener, only one is removed.
* </p>
*
* @param l a listener to receive connection status notifications.
* @param f a filter to select which notifications are to be delivered to the listener. Can be null.
* @param handback an object to be given to the listener along with each notification. Can be null.
* @throws ListenerNotFoundException if the listener is not registered with this <code>JMXConnector</code>, or is not registered
* with the given filter and handback.
* @see #removeConnectionNotificationListener(NotificationListener)
* @see #addConnectionNotificationListener
* @see NotificationEmitter#removeNotificationListener
*/
public void removeConnectionNotificationListener(NotificationListener l, NotificationFilter f, Object handback)
throws ListenerNotFoundException {
connectionNotifier.removeNotificationListener(l, f, handback);
}
/**
* <p>
* Gets this connection's ID from the connector server. For a given connector server, every connection will have a
* unique id which does not change during the lifetime of the connection.
* </p>
*
* @return the unique ID of this connection. This is the same as the ID that the connector server includes in its
* {@link JMXConnectionNotification}s. The {@link javax.management.remote package description} describes the
* conventions for connection IDs.
*/
public String getConnectionId() {
return connectionId;
}
public CamelContext getCamelContext() {
if (camelContext == null) {
LOG.warn("No CamelContext injected so creating a default implementation");
// TODO should we barf or create a default one?
camelContext = new DefaultCamelContext();
}
return camelContext;
}
public void setCamelContext(CamelContext camelContext) {
this.camelContext = camelContext;
}
private void sendConnectionNotificationOpened() {
JMXConnectionNotification notification = new JMXConnectionNotification(JMXConnectionNotification.OPENED, this,
getConnectionId(), notificationNumber.incrementAndGet(), "Connection opened", null);
connectionNotifier.sendNotification(notification);
}
private void sendConnectionNotificationClosed() {
JMXConnectionNotification notification = new JMXConnectionNotification(JMXConnectionNotification.CLOSED, this,
getConnectionId(), notificationNumber.incrementAndGet(), "Connection closed", null);
connectionNotifier.sendNotification(notification);
}
private void sendConnectionNotificationFailed(String message) {
JMXConnectionNotification notification = new JMXConnectionNotification(JMXConnectionNotification.FAILED, this,
getConnectionId(), notificationNumber.incrementAndGet(), message, null);
connectionNotifier.sendNotification(notification);
}
}