package rocks.inspectit.agent.java.connection.impl;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.esotericsoftware.kryonet.rmi.RemoteObject;
import rocks.inspectit.agent.java.connection.FailFastRemoteMethodCall;
import rocks.inspectit.agent.java.connection.IConnection;
import rocks.inspectit.agent.java.connection.RegistrationException;
import rocks.inspectit.agent.java.connection.ServerUnavailableException;
import rocks.inspectit.shared.all.cmr.service.IAgentService;
import rocks.inspectit.shared.all.cmr.service.IAgentStorageService;
import rocks.inspectit.shared.all.cmr.service.IKeepAliveService;
import rocks.inspectit.shared.all.cmr.service.ServiceInterface;
import rocks.inspectit.shared.all.communication.DefaultData;
import rocks.inspectit.shared.all.communication.message.IAgentMessage;
import rocks.inspectit.shared.all.exception.BusinessException;
import rocks.inspectit.shared.all.instrumentation.classcache.Type;
import rocks.inspectit.shared.all.instrumentation.config.impl.AgentConfig;
import rocks.inspectit.shared.all.instrumentation.config.impl.InstrumentationDefinition;
import rocks.inspectit.shared.all.instrumentation.config.impl.JmxAttributeDescriptor;
import rocks.inspectit.shared.all.kryonet.Client;
import rocks.inspectit.shared.all.kryonet.rmi.ObjectSpace;
import rocks.inspectit.shared.all.spring.logger.Log;
/**
* Implements the {@link IConnection} interface using the kryo-net.
*
* @author Patrice Bouillet
* @author Ivan Senic
*
*/
@Component
public class KryoNetConnection implements IConnection {
/**
* The logger of the class.
*/
@Log
Logger log;
/**
* The kryonet client to connect to the CMR.
*/
@Autowired
private Client client;
/**
* The agent storage remote object which will be used to send the measurements to.
*/
private IAgentStorageService agentStorageService;
/**
* Agent service.
*/
private IAgentService agentService;
/**
* The keep-alive service remote object to send keep-alive messages.
*/
private IKeepAliveService keepAliveService;
/**
* Defines if there was a connection exception before. Used for throttling the info log
* messages.
*/
private boolean connectionException = false;
/**
* The list of all network interfaces.
*/
private List<String> networkInterfaces;
/**
* Monitor for signaling reconnection of the connection.
*/
private final Object reconnectionMonitor = new Object();
/**
* {@inheritDoc}
*/
@Override
public void connect(String host, int port) throws ConnectException {
if (!isConnected()) {
try {
if (!connectionException) {
log.info("KryoNet: Connecting to " + host + ":" + port);
}
startClient(host, port);
log.info("KryoNet: Connection established!");
connectionException = false;
} catch (Exception exception) {
if (!connectionException) {
log.info("KryoNet: Connection to the server failed.");
}
connectionException = true;
stopClient();
if (log.isTraceEnabled()) {
log.trace("connect()", exception);
}
ConnectException e = new ConnectException(exception.getMessage());
e.initCause(exception);
throw e; // NOPMD root cause exception is set
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void reconnect() throws ConnectException {
if ((null != client) && !isConnected()) {
try {
reconnectClient();
log.info("KryoNet: Reconnection successful!");
connectionException = false;
synchronized (reconnectionMonitor) {
reconnectionMonitor.notifyAll();
}
} catch (Exception exception) {
connectionException = true;
ConnectException e = new ConnectException(exception.getMessage());
e.initCause(exception);
throw e; // NOPMD root cause exception is set
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void disconnect() {
stopClient();
agentStorageService = null; // NOPMD
agentService = null; // NOPMD
keepAliveService = null; // NOPMD
}
/**
* Starts the client and tries to make a connection to the given host/port.
*
* @param host
* Host IP address.
* @param port
* Port to connect to.
*
* @throws IOException
* If {@link IOException} occurs during the connection.
*/
private void startClient(String host, int port) throws IOException {
if (null != client) {
client.start();
client.connect(5000, host, port);
bindServices();
}
}
/**
* Reconnects the client if it's not already connected.
*
* @throws IOException
* If {@link IOException} occurs during the connection.
*/
private void reconnectClient() throws IOException {
if (!isConnected()) {
client.reconnect();
bindServices();
}
}
/**
* Closes the client (disconnects the client) if the client is connected.
*/
private void disconnectClient() {
if (isConnected()) {
client.close();
}
}
/**
* Stops the client.
*/
private void stopClient() {
if (null != client) {
client.stop();
}
}
/**
* Binds services if client is connected.
*/
private void bindServices() {
if (client.isConnected()) {
int agentStorageServiceId = IAgentStorageService.class.getAnnotation(ServiceInterface.class).serviceId();
agentStorageService = ObjectSpace.getRemoteObject(client, agentStorageServiceId, IAgentStorageService.class);
((RemoteObject) agentStorageService).setNonBlocking(true);
((RemoteObject) agentStorageService).setTransmitReturnValue(false);
int agentServiceServiceId = IAgentService.class.getAnnotation(ServiceInterface.class).serviceId();
agentService = ObjectSpace.getRemoteObject(client, agentServiceServiceId, IAgentService.class);
((RemoteObject) agentService).setNonBlocking(false);
((RemoteObject) agentService).setTransmitReturnValue(true);
int keepAliveServiceId = IKeepAliveService.class.getAnnotation(ServiceInterface.class).serviceId();
keepAliveService = ObjectSpace.getRemoteObject(client, keepAliveServiceId, IKeepAliveService.class);
((RemoteObject) keepAliveService).setNonBlocking(true);
((RemoteObject) keepAliveService).setTransmitReturnValue(false);
}
}
/**
* {@inheritDoc}
*/
@Override
public void sendKeepAlive(final long platformId) throws ServerUnavailableException {
if (!isConnected()) {
throw new ServerUnavailableException();
}
FailFastRemoteMethodCall<IKeepAliveService, Void> call = new FailFastRemoteMethodCall<IKeepAliveService, Void>(keepAliveService) {
@Override
protected Void performRemoteCall(IKeepAliveService service) {
service.sendKeepAlive(platformId);
return null;
}
};
try {
call.makeCall();
} catch (ExecutionException e) {
// there should be no execution exception
log.error("Exception thrown while trying to send keep-alive signal to the server.", e);
} catch (ServerUnavailableException e) {
if (!e.isServerTimeout()) {
disconnectClient();
}
throw e;
}
}
/**
* {@inheritDoc}
*/
@Override
public AgentConfig register(final String agentName, final String version) throws ServerUnavailableException, RegistrationException, BusinessException {
if (!isConnected()) {
throw new ServerUnavailableException();
}
// ensure network interfaces
try {
if (null == networkInterfaces) {
networkInterfaces = getNetworkInterfaces();
}
} catch (SocketException socketException) {
log.error("Could not obtain network interfaces from this machine!");
if (log.isTraceEnabled()) {
log.trace("unregister(List,String)", socketException);
}
throw new RegistrationException("Could not un-register the platform", socketException);
}
// make call
FailFastRemoteMethodCall<IAgentService, AgentConfig> call = new FailFastRemoteMethodCall<IAgentService, AgentConfig>(agentService) {
@Override
protected AgentConfig performRemoteCall(IAgentService service) throws Exception {
return agentService.register(networkInterfaces, agentName, version);
}
};
try {
return call.makeCall();
} catch (ExecutionException executionException) {
if (log.isTraceEnabled()) {
log.trace("register(String, String)", executionException);
}
// check for business exception
if (executionException.getCause() instanceof BusinessException) {
throw ((BusinessException) executionException.getCause()); // NOPMD
}
throw new RegistrationException("Could not register the platform", executionException.getCause()); // NOPMD
} catch (ServerUnavailableException e) {
if (!e.isServerTimeout()) {
disconnectClient();
}
throw e;
}
}
/**
* {@inheritDoc}
*/
@Override
public void unregister(final long platformIdent) throws ServerUnavailableException, RegistrationException, BusinessException {
if (!isConnected()) {
throw new ServerUnavailableException();
}
// make call
FailFastRemoteMethodCall<IAgentService, Void> call = new FailFastRemoteMethodCall<IAgentService, Void>(agentService) {
@Override
protected Void performRemoteCall(IAgentService service) throws Exception {
service.unregister(platformIdent);
return null;
}
};
try {
call.makeCall();
} catch (ExecutionException executionException) {
if (log.isTraceEnabled()) {
log.trace("unregister(long)", executionException);
}
// check for business exception
if (executionException.getCause() instanceof BusinessException) {
throw ((BusinessException) executionException.getCause()); // NOPMD
}
throw new RegistrationException("Could not un-register the platform", executionException.getCause()); // NOPMD
} catch (ServerUnavailableException e) {
if (!e.isServerTimeout()) {
disconnectClient();
}
throw e;
}
}
/**
* {@inheritDoc}
*/
@Override
public void sendDataObjects(List<? extends DefaultData> measurements) throws ServerUnavailableException {
if (!isConnected()) {
throw new ServerUnavailableException();
}
if ((null != measurements) && !measurements.isEmpty()) {
try {
AddDataObjects remote = new AddDataObjects(agentStorageService, measurements);
remote.makeCall();
} catch (ExecutionException executionException) {
// there should be no execution exception
log.error("Could not send data objects", executionException);
} catch (ServerUnavailableException e) {
if (!e.isServerTimeout()) {
disconnectClient();
}
throw e;
}
}
}
/**
* {@inheritDoc}
*/
@Override
public InstrumentationDefinition analyze(final long platformIdent, final String hash, final Type type) throws ServerUnavailableException, BusinessException {
if (!isConnected()) {
throw new ServerUnavailableException();
}
// make call
FailFastRemoteMethodCall<IAgentService, InstrumentationDefinition> call = new FailFastRemoteMethodCall<IAgentService, InstrumentationDefinition>(agentService) {
@Override
protected InstrumentationDefinition performRemoteCall(IAgentService service) throws Exception {
return agentService.analyze(platformIdent, hash, type);
}
};
try {
return call.makeCall();
} catch (ExecutionException executionException) {
if (log.isTraceEnabled()) {
log.trace("analyze(long,String,Type)", executionException);
}
// check for business exception
if (executionException.getCause() instanceof BusinessException) {
throw ((BusinessException) executionException.getCause()); // NOPMD
}
// otherwise we log and return null as it's unexpected exception for us
log.error("Could not get instrumentation result", executionException);
return null;
} catch (ServerUnavailableException e) {
if (!e.isServerTimeout()) {
disconnectClient();
}
throw e;
}
}
/**
* {@inheritDoc}
*/
@Override
public void instrumentationApplied(final long platformIdent, Map<Long, long[]> methodToSensorMap) throws ServerUnavailableException {
if (!isConnected()) {
throw new ServerUnavailableException();
}
if (MapUtils.isNotEmpty(methodToSensorMap)) {
try {
InstrumentationAppliedCall call = new InstrumentationAppliedCall(agentService, platformIdent, methodToSensorMap);
call.makeCall();
} catch (ExecutionException executionException) {
// there should be no execution exception
log.error("Could not sent instrumented method ids", executionException);
} catch (ServerUnavailableException e) {
if (!e.isServerTimeout()) {
disconnectClient();
}
throw e;
}
}
}
/**
* {@inheritDoc}
*/
@Override
public Collection<JmxAttributeDescriptor> analyzeJmxAttributes(final long platformIdent, final Collection<JmxAttributeDescriptor> descriptors) throws ServerUnavailableException {
if (!isConnected()) {
throw new ServerUnavailableException();
}
// make call
FailFastRemoteMethodCall<IAgentService, Collection<JmxAttributeDescriptor>> call = new FailFastRemoteMethodCall<IAgentService, Collection<JmxAttributeDescriptor>>(agentService) {
@Override
protected Collection<JmxAttributeDescriptor> performRemoteCall(IAgentService service) throws Exception {
return agentService.analyzeJmxAttributes(platformIdent, descriptors);
}
};
try {
return call.makeCall();
} catch (ExecutionException executionException) {
if (log.isTraceEnabled()) {
log.trace("analyzeJmxAttributes(long,Collection)", executionException);
}
// otherwise we log and return null as it's unexpected exception for us
log.error("Could not get jmx attribute analyze result", executionException);
return Collections.emptyList();
} catch (ServerUnavailableException e) {
if (!e.isServerTimeout()) {
disconnectClient();
}
throw e;
}
}
/**
* {@inheritDoc}
*/
@Override
public List<IAgentMessage<?>> fetchAgentMessages(final long platformIdent) throws ServerUnavailableException {
if (!isConnected()) {
throw new ServerUnavailableException();
}
// make call
FailFastRemoteMethodCall<IAgentService, List<IAgentMessage<?>>> call = new FailFastRemoteMethodCall<IAgentService, List<IAgentMessage<?>>>(agentService) {
@Override
protected List<IAgentMessage<?>> performRemoteCall(IAgentService service) throws Exception {
return agentService.fetchAgentMessages(platformIdent);
}
};
try {
return call.makeCall();
} catch (ExecutionException executionException) {
if (log.isTraceEnabled()) {
log.trace("fetchAgentMessages(long,Collection)", executionException);
}
// otherwise we log and return null as it's unexpected exception for us
log.error("Could not fetch agent messages.", executionException);
return Collections.emptyList();
} catch (ServerUnavailableException e) {
if (!e.isServerTimeout()) {
disconnectClient();
}
throw e;
}
}
/**
* Loads all the network interfaces and transforms the enumeration to the list of strings
* containing all addresses.
*
* @return List of all network interfaces.
* @throws SocketException
* If {@link SocketException} occurs.
*/
private List<String> getNetworkInterfaces() throws SocketException {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
List<String> networkInterfaces = new ArrayList<String>();
while (interfaces.hasMoreElements()) {
NetworkInterface networkInterface = interfaces.nextElement();
Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress address = addresses.nextElement();
networkInterfaces.add(address.getHostAddress());
}
}
return networkInterfaces;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isConnected() {
return (null != client) && client.isConnected();
}
/**
* {@inheritDoc}
*/
@Override
public Object getReconnectionMonitor() {
return reconnectionMonitor;
}
}