/*
* NOTE: This copyright does *not* cover user programs that use HQ
* program services by normal system calls through the application
* program interfaces provided as part of the Hyperic Plug-in Development
* Kit or the Hyperic Client Development Kit - this is merely considered
* normal use of the program, and does *not* fall under the heading of
* "derived work".
*
* Copyright (C) [2004-2008], Hyperic, Inc.
* This file is part of HQ.
*
* HQ is free software; you can redistribute it and/or modify
* it under the terms version 2 of the GNU General Public License as
* published by the Free Software Foundation. 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*/
package org.hyperic.hq.transport;
import java.lang.reflect.Constructor;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import org.hyperic.hq.transport.util.TransportUtils;
import org.jboss.remoting.InvokerLocator;
import org.jboss.remoting.transporter.TransporterServer;
/**
* The transport for the HQ agent. Services hosted by this transport should be
* registered before the transport is started.
*/
public class AgentTransport {
private final Object _lock = new Object();
private final PollerClient _pollerClient;
private final TransporterServer _server;
private final boolean _unidirectional;
private final InvokerLocator _remoteEndpointLocator;
private boolean _stopped;
/**
* Creates an instance.
*
* @param serverTransportAddr The listening socket address on the server transport.
* @param path The invoker locator path or <code>null</code>.
* @param encrypted <code>true</code> if using encrypted communication;
* <code>false</code> if not encrypted.
* @param agentToken The agent token uniquely identifying the agent.
* @param unidirectional <code>true</code> to use a unidirectional transport;
* <code>false</code> to use a bidirectional transport.
* @param pollingFrequency The polling frequency in milliseconds.
* This parameter is ignored for bidirectional transports.
* @param asyncThreadPoolSize The thread pool size for the asynchronous invoker.
* This parameter is ignored for bidirectional transports.
* @throws ClassNotFoundException if this is a .ORG instance and attempting
* to use the unidirectional transport.
* @throws Exception if instance creation fails.
*/
public AgentTransport(InetSocketAddress serverTransportAddr,
String path,
boolean encrypted,
String agentToken,
boolean unidirectional,
long pollingFrequency,
int asyncThreadPoolSize) throws Exception {
_unidirectional = unidirectional;
InvokerLocator remotingServerInvokerLocator;
if (_unidirectional) {
_pollerClient = createPollerClient(serverTransportAddr,
path,
encrypted,
pollingFrequency,
agentToken,
asyncThreadPoolSize);
// for a unidirectional agent - we can use a local invoker when registering
// services - since the poller client is in the agent's vm
remotingServerInvokerLocator = getLocalInvokerLocator();
_remoteEndpointLocator = _pollerClient.getRemoteEndpointLocator();
} else {
// TODO - need to specify the invoker locator for bidirectional
// (both remoting server invoker locator and remote end point invoker locator)
remotingServerInvokerLocator = null;
_remoteEndpointLocator = null;
throw new UnsupportedOperationException("bidirectional not supported yet");
}
_server = TransporterServer.createTransporterServer(remotingServerInvokerLocator,
new BootStrapService());
}
/**
* @return The invoker locator for the remote end point to which this
* transport is connected.
*/
public InvokerLocator getRemoteEndpointLocator() {
return _remoteEndpointLocator;
}
/**
* Create the poller client. A ClassNotFoundException is thrown if this is
* a .ORG instance. The unidirectional transport that requires the poller
* client is only supported in EE.
*/
private PollerClient createPollerClient(InetSocketAddress serverTransportAddr,
String path,
boolean encrypted,
long pollingFrequency,
String agentToken,
int asyncThreadPoolSize)
throws ClassNotFoundException, Exception {
Class clazz;
try {
clazz = TransportUtils.tryLoadUnidirectionalTransportPollerClient();
} catch (ClassNotFoundException e) {
throw new ClassNotFoundException(
"Unidirectional transport is not available in .ORG");
}
Constructor constructor = clazz.getConstructor(
new Class[]{InetSocketAddress.class,
String.class,
Boolean.TYPE,
Long.TYPE,
String.class,
Integer.TYPE});
return (PollerClient)constructor.newInstance(
new Object[]{serverTransportAddr,
path,
Boolean.valueOf(encrypted),
new Long(pollingFrequency),
agentToken,
new Integer(asyncThreadPoolSize)});
}
/**
* Register a service to be hosted by this transport.
*
* @param serviceInterface The service interface class.
* @param serviceImpl The service implementation.
* @throws IllegalArgumentException if the service does not implement the
* interface.
* @throws Exception if service registration fails.
*/
public void registerService(Class serviceInterface, Object serviceImpl)
throws Exception {
verifyServiceImplementsInterface(serviceInterface, serviceImpl);
_server.addHandler(serviceImpl, serviceInterface.getName());
}
/**
* Update the agent token uniquely identifying the agent.
*
* @param agentToken The agent token.
* @throws NullPointerException if the agent token is <code>null</code>.
*/
public void updateAgentToken(String agentToken) {
if (agentToken == null) {
throw new NullPointerException("agent token is null");
}
_pollerClient.updateAgentToken(agentToken);
}
/**
* Start the transport.
*
* @throws Exception
*/
public void start() throws Exception {
if (isStopped()) {
return;
}
_server.start();
if (_unidirectional) {
_pollerClient.start();
}
}
/**
* Stop the transport. Once stopped, it cannot be started again.
*/
public void stop() throws InterruptedException {
if (_unidirectional) {
_pollerClient.stop();
}
_server.stop();
setStopped();
}
private InvokerLocator getLocalInvokerLocator() {
// Suspected bug in JBoss: it looks like the stop() method of TransportServer doesn't
// internally shut down its Connector in a timely synchronous fashion. This is mostly
// harmless, except in unittests, which create and destroy AgentTransports one after
// another. Putting in params with a timestamp is a cheesy workaround.
Map params = new HashMap(1);
params.put("timestamp", String.valueOf(System.currentTimeMillis()));
return new InvokerLocator("local", "localhost", -1, null, params);
}
private void verifyServiceImplementsInterface(Class serviceInterface, Object serviceImpl) {
Class[] interfaces = serviceImpl.getClass().getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
Class interfaceClazz = interfaces[i];
if (interfaceClazz.equals(serviceInterface)) {
return;
}
}
throw new IllegalArgumentException("service: "+serviceImpl.getClass()+
" does not implement interface: "+serviceInterface);
}
private void setStopped() {
synchronized (_lock) {
_stopped = true;
}
}
private boolean isStopped() {
synchronized (_lock) {
return _stopped;
}
}
private static class BootStrapService {
void doNothing() {
// no-op
}
}
}