blob: 4d6f581a0dd691442330089b2d32ab803b5940af [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2021 the Eclipse BaSyx Authors
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
******************************************************************************/
package org.eclipse.basyx.vab.protocol.basyx.connector;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import org.eclipse.basyx.vab.exception.provider.ProviderException;
import org.eclipse.basyx.vab.protocol.api.IBaSyxConnector;
import org.eclipse.basyx.vab.protocol.basyx.CoderTools;
import org.eclipse.basyx.vab.protocol.basyx.server.VABBaSyxTCPInterface;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* BaSyx connector class
*
* @author kuhn, pschorn, schnicke
*
*/
public class BaSyxConnector implements IBaSyxConnector {
private Logger logger = LoggerFactory.getLogger(BaSyxConnector.class);
private InetSocketAddress serverSocketAddress;
private SocketChannel channelToProvider;
/**
* Constructor that creates a connection.
*
* This constructor connects to port at name.
*/
public BaSyxConnector(String hostName, int port) {
// Base constructor
super();
// Exception handling
try {
// Resolve address
InetAddress serverIPAddress = InetAddress.getByName(hostName);
serverSocketAddress = new InetSocketAddress(serverIPAddress, port);
} catch (IOException e) {
// Print stack trace
logger.error("Exception in BaSyxConnector", e);
}
}
/**
* Close connection
*/
private void closeConnection() {
// Try to close connection
try {
channelToProvider.close();
} catch (IOException e) {
// Print stack trace
logger.error("Exception in closeConnection", e);
}
}
/**
* Invoke a BaSyx operation in a remote provider
*/
protected synchronized String invokeBaSyx(byte[] call) {
// Catch exceptions
try {
// Send byte array (BaSyx operation) via channel to provider
ByteBuffer txBuffer = ByteBuffer.wrap(call);
// Channel to provider
channelToProvider = SocketChannel.open();
// - Setup channel: set to blocking and connect to provider
channelToProvider.configureBlocking(true);
channelToProvider.connect(serverSocketAddress);
channelToProvider.write(txBuffer);
// Read response
// - Wait for leading 4 byte header that contains frame length
ByteBuffer rxBuffer1 = ByteBuffer.allocate(4);
// System.out.println("RX1");
readBytes(rxBuffer1, 4);
// System.out.println("RX1-d");
int frameSize = CoderTools.getInt32(rxBuffer1.array(), 0);
// Wait for frame to arrive
ByteBuffer rxBuffer2 = ByteBuffer.allocate(frameSize);
// System.out.println("RX2");
readBytes(rxBuffer2, frameSize);
// System.out.println("RX2-d");
byte[] rxFrame = rxBuffer2.array();
// Return received data
// Result check
if ((rxFrame == null) || (rxFrame.length < 2)) return null;
// - FIXME: Check result on position 0
// Extract response
int jsonResultLen = CoderTools.getInt32(rxFrame, 1);
String jsonResult = new String(rxFrame, 1 + 4, jsonResultLen);
// Close connection to prevent unused open channels
closeConnection();
// Return result
return jsonResult.toString();
} catch (IOException e) {
// Print stack trace
logger.error("Exception in invokeBaSyx", e);
}
// Indicate error
return null;
}
/**
* Read a number of bytes
*/
protected void readBytes(ByteBuffer bytes, int expectedBytes) {
// Exception handling
try {
// System.out.println("-Reading:"+expectedBytes);
// Read bytes until buffer is full
while (bytes.position() < expectedBytes) {
// System.out.println("-Pos:"+bytes.position());
channelToProvider.read(bytes);
}
// System.out.println("-Read:"+expectedBytes);
} catch (IOException e) {
// Output exception
logger.error("Exception in readBytes", e);
}
}
/**
* Invoke a BaSys get operation via HTTP
*/
@Override
public String getModelPropertyValue(String servicePath) {
byte[] call = createCall(servicePath, VABBaSyxTCPInterface.BASYX_GET);
// Invoke BaSyx call and return result
return invokeBaSyx(call);
}
/**
* Invoke a Basys Set operation. Sets or overrides existing property, operation
* or event.
*
* @throws ProviderException
* that carries the Exceptions thrown on the server
*/
@Override
public String setModelPropertyValue(String servicePath, String newValue) {
byte[] call = createCall(servicePath, newValue, VABBaSyxTCPInterface.BASYX_SET);
// Invoke BaSyx call and return result
return invokeBaSyx(call);
}
/**
* Invoke a BaSys Create operation
*/
@Override
public String createValue(String servicePath, String newValue) throws ProviderException {
byte[] call = createCall(servicePath, newValue, VABBaSyxTCPInterface.BASYX_CREATE);
// Invoke BaSyx call and return result
return invokeBaSyx(call);
}
/**
* Invoke a Basys invoke operation.
*/
@Override
public String invokeOperation(String servicePath, String parameters) throws ProviderException {
byte[] call = createCall(servicePath, parameters, VABBaSyxTCPInterface.BASYX_INVOKE);
// Invoke BaSyx call and return result
return invokeBaSyx(call);
}
/**
* Invoke a Basys delete operation. Deletes any resource under the given path
*
* @throws ProviderException
* that carries the Exceptions thrown on the server
*/
@Override
public String deleteValue(String servicePath) throws ProviderException {
byte[] call = createCall(servicePath, VABBaSyxTCPInterface.BASYX_DELETE);
// Invoke BaSyx call and return result
return invokeBaSyx(call);
}
/**
* Invoke a Basys delete operation. Deletes an entry from a map or collection by
* the given key
*
* @throws ProviderException
* that carries the Exceptions thrown on the server
*/
@Override
public String deleteValue(String servicePath, String jsonObject) throws ProviderException {
byte[] call = createCall(servicePath, jsonObject, VABBaSyxTCPInterface.BASYX_DELETE);
// Invoke BaSyx call and return result
return invokeBaSyx(call);
}
/**
* Create non-parameterized call that can be used as an argument to the
* invokeBaSyx function
*
* @param servicePath
* @param jsonObject
* @return
*/
private byte[] createCall(String servicePath, byte callType) {
// Create call
byte[] call = new byte[4 + 1 + 4 + servicePath.length()];
// - Encode size does not include leading four bytes
CoderTools.setInt32(call, 0, call.length - 4);
// - Encode operation GET
CoderTools.setInt8(call, 4, callType);
// - Encode path length and path
CoderTools.setInt32(call, 5, servicePath.length());
CoderTools.setString(call, 9, servicePath);
return call;
}
/**
* Create parameterized byte call that can be used as an argument to the
* invokeBaSyx function
*
* @param servicePath
* @param newValue
* @param callType
* @return
*/
private byte[] createCall(String servicePath, String newValue, byte callType) {
// Create call
byte[] call = new byte[4 + 1 + 4 + servicePath.length() + 4 + newValue.length()];
// - Encode size does not include leading four bytes
CoderTools.setInt32(call, 0, call.length - 4);
// - Encode operation SET
CoderTools.setInt8(call, 4, callType);
// - Encode path
CoderTools.setInt32(call, 5, servicePath.length());
CoderTools.setString(call, 9, servicePath);
// - Encode value
CoderTools.setInt32(call, 9 + servicePath.length(), newValue.length());
CoderTools.setString(call, 9 + servicePath.length() + 4, newValue);
return call;
}
/**
* Get string representation of endpoint for given path for debugging.
* @param path Requested path
* @return String representing requested endpoint
*/
@Override
public String getEndpointRepresentation(String path) {
return "basyx://" + serverSocketAddress.getHostString() + ":" + serverSocketAddress.getPort() + "/" + path;
}
}