/**
* Copyright (C) 2010-14 diirt developers. See COPYRIGHT.TXT
* All rights reserved. Use is subject to license terms. See LICENSE.TXT
*/
package org.diirt.support.pva.rpcservice.rpcclient;
import org.epics.pvaccess.client.*;
import org.epics.pvaccess.client.rpc.RPCClient;
import org.epics.pvaccess.client.rpc.RPCClientRequester;
import org.epics.pvaccess.server.rpc.RPCRequestException;
import org.epics.pvdata.factory.StatusFactory;
import org.epics.pvdata.pv.MessageType;
import org.epics.pvdata.pv.PVStructure;
import org.epics.pvdata.pv.Status;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author msekoranja
* @author dkumar
*
*/
class RPCClientImpl implements RPCClient, ChannelRequester, ChannelRPCRequester {
private static final Logger logger = Logger.getLogger(RPCClientImpl.class.getName());
private final RPCClientRequester serviceRequester;
private final Channel channel;
private final CountDownLatch connectedSignaler = new CountDownLatch(1);
private final AtomicBoolean requestPending = new AtomicBoolean(false);
private volatile ChannelRPC channelRPC = null;
private final Object resultMonitor = new Object();
private Status status = null;
private PVStructure result = null;
public RPCClientImpl(String hostName, String channelName) {
this(null,hostName, channelName);
}
public RPCClientImpl(RPCClientRequester requester, String hostName, String channelName) {
this.serviceRequester = requester;
org.epics.pvaccess.ClientFactory.start();
ChannelProvider channelProvider =
ChannelProviderRegistryFactory.getChannelProviderRegistry()
.getProvider(org.epics.pvaccess.ClientFactory.PROVIDER_NAME);
if (hostName == null) {
this.channel = channelProvider.createChannel(channelName, this, ChannelProvider.PRIORITY_DEFAULT);
}
else {
this.channel = channelProvider.createChannel(channelName, this, ChannelProvider.PRIORITY_DEFAULT, hostName);
}
channel.createChannelRPC(this, null);
}
/* (non-Javadoc)
* @see org.epics.pvaccess.client.rpc.RPCClient#destroy()
*/
@Override
public void destroy() {
channel.destroy();
//org.epics.pvaccess.ClientFactory.stop();
}
/* (non-Javadoc)
* @see org.epics.pvaccess.client.rpc.RPCClient#waitConnect(double)
*/
@Override
public boolean waitConnect(double timeout) {
try {
return connectedSignaler.await((long)(timeout*1000), TimeUnit.MILLISECONDS) && channelRPC != null;
} catch (InterruptedException e) {
return false;
}
}
private ChannelRPC checkConnectAndPending(double timeout)
{
ChannelRPC rpc;
while ((rpc = channelRPC) == null)
{
if (timeout == 0 || !waitConnect(timeout))
throw new IllegalStateException("ChannelRPC never connected.");
}
// check pending
if (requestPending.getAndSet(true))
throw new IllegalStateException("one request already pending");
return rpc;
}
/* (non-Javadoc)
* @see org.epics.pvaccess.client.rpc.RPCClient#request(org.epics.pvdata.pv.PVStructure, double)
*/
@Override
public PVStructure request(PVStructure pvArgument, double timeout)
throws RPCRequestException {
long startTime = System.currentTimeMillis();
long timeoutMs = (long)(timeout*1000);
synchronized (resultMonitor) {
sendRequestInternal(timeout, pvArgument);
long timeLeft = Math.max(timeoutMs - (System.currentTimeMillis() - startTime), 0);
if (waitResponse(timeLeft))
{
if (status.isSuccess())
return result;
else
{
if (status.getStackDump() == null)
throw new RPCRequestException(status.getType(), status.getMessage());
else
throw new RPCRequestException(status.getType(), status.getMessage() + ", cause:\n" + status.getStackDump());
}
}
else
{
// timeout
throw new RPCRequestException(Status.StatusType.ERROR, "timeout");
}
}
}
/* (non-Javadoc)
* @see org.epics.pvaccess.client.rpc.RPCClient#sendRequest(org.epics.pvdata.pv.PVStructure)
*/
@Override
public void sendRequest(PVStructure pvArgument) {
sendRequestInternal(0, pvArgument);
}
private void sendRequestInternal(double connectTimeout, PVStructure pvArgument)
{
ChannelRPC rpc = checkConnectAndPending(connectTimeout);
synchronized (resultMonitor) {
status = null;
result = null;
}
try {
rpc.request(pvArgument);
} catch (Throwable th) {
requestDone(
StatusFactory.getStatusCreate().createStatus(
Status.StatusType.ERROR, "failed to send a RPC request", th),
rpc,
null
);
}
}
/* (non-Javadoc)
* @see org.epics.pvaccess.client.rpc.RPCClient#waitResponse(double)
*/
@Override
public boolean waitResponse(double timeout) {
synchronized (resultMonitor) {
long timeoutMs = (long)(timeout*1000);
// NOTE: spurious wakeup proof code
long startTime = System.currentTimeMillis();
long diff;
while (status == null && (diff = (System.currentTimeMillis() - startTime)) < timeoutMs)
{
try {
resultMonitor.wait(timeoutMs - diff);
} catch (InterruptedException e) {
return false;
}
}
return status != null;
}
}
@Override
public String getRequesterName() {
return serviceRequester != null ? serviceRequester.getRequesterName() : getClass().getName();
}
@Override
public void message(String message, MessageType messageType) {
if (serviceRequester != null)
serviceRequester.message(message, messageType);
else
logger.finer(getRequesterName() + ": [" + messageType + "] " + message);
}
@Override
public void channelCreated(Status status, Channel channel) {
logger.finer("Channel '" + channel.getChannelName() + "' created with status: " + status + ".");
}
@Override
public void channelStateChange(Channel channel, Channel.ConnectionState connectionState) {
logger.finer("Channel '" + channel.getChannelName() + "' " + connectionState + ".");
if (connectionState != Channel.ConnectionState.CONNECTED && requestPending.get())
requestDone(
StatusFactory.getStatusCreate().createStatus(
Status.StatusType.ERROR, "channel " + connectionState, null),
channelRPC,
null
);
}
@Override
public void channelRPCConnect(Status status, ChannelRPC channelRPC) {
logger.finer("ChannelRPC for '" + channel.getChannelName() + "' connected with status: " + status + ".");
this.channelRPC = channelRPC;
connectedSignaler.countDown();
if (serviceRequester != null)
{
try {
serviceRequester.connectResult(this, status);
} catch (Throwable th) {
logger.log(Level.SEVERE, "Unhandled exception in RPCClientRequester.connectResult().", th);
}
}
}
@Override
public void requestDone(Status status, ChannelRPC channelRPC, PVStructure result) {
logger.finer("requestDone for '" + channel.getChannelName() + "' called with status: " + status + ".");
requestPending.set(false);
synchronized (resultMonitor) {
this.status = status;
this.result = result;
resultMonitor.notifyAll();
}
if (serviceRequester != null)
{
try {
serviceRequester.requestResult(this, status, result);
} catch (Throwable th) {
logger.log(Level.SEVERE, "Unhandled exception in RPCClientRequester.requestResult().", th);
}
}
}
}