package com.linkedin.databus.client;
/*
*
* Copyright 2013 LinkedIn Corp. All rights reserved
*
* Licensed 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.
*
*/
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
import javax.management.MBeanServer;
import org.apache.log4j.Logger;
import com.linkedin.databus.client.ConnectionState.StateId;
import com.linkedin.databus.client.pub.ServerInfo;
import com.linkedin.databus.client.pub.mbean.UnifiedClientStats;
import com.linkedin.databus.core.DatabusComponentStatus;
import com.linkedin.databus.core.DatabusComponentStatus.Status;
import com.linkedin.databus.core.DbusEventBuffer;
import com.linkedin.databus.core.DbusEventFactory;
import com.linkedin.databus.core.async.AbstractActorMessageQueue;
import com.linkedin.databus.core.async.LifecycleMessage;
import com.linkedin.databus2.core.BackoffTimerStaticConfig;
import com.linkedin.databus2.core.mbean.DatabusReadOnlyStatus;
public abstract class BasePullThread extends AbstractActorMessageQueue
{
protected Set<ServerInfo> _servers;
protected int _curServerIdx = -1;
protected ServerInfo _curServer = null;
protected final ConnectionState _currentState;
protected final DatabusComponentStatus _status;
protected final DatabusReadOnlyStatus _statusMbean;
protected DatabusSourcesConnection _sourcesConn;
private final DbusEventFactory _eventFactory;
/* Flag to mark delaying tear connection after server-set change if client is waiting for response */
private boolean tearConnAfterResponse = false;
private final MBeanServer _mbeanServer;
/* Filter to clear message queue before adding PickServer */
private final MessageQueueFilter pickServerFilter = new PickServerEnqueueFilter();
public BasePullThread(String name,
BackoffTimerStaticConfig pullerRetries,
DatabusSourcesConnection sourcesConn,
DbusEventBuffer dbusEventBuffer,
ConnectionStateFactory connStateFactory,
Set<ServerInfo> servers,
MBeanServer mbeanServer,
DbusEventFactory eventFactory,
Logger log)
{
super(name,
pullerRetries,
sourcesConn.getConnectionConfig().isPullerMessageQueueLoggingEnabled(),
log);
_sourcesConn = sourcesConn;
_currentState = connStateFactory.create(dbusEventBuffer);
if ( null != servers)
_servers = new TreeSet<ServerInfo>(servers);
else
_servers = new TreeSet<ServerInfo>();
_eventFactory = eventFactory;
_mbeanServer = mbeanServer;
_status = new DatabusComponentStatus(name, pullerRetries);
_statusMbean = new DatabusReadOnlyStatus(getName(), _status, -1);
_statusMbean.registerAsMbean(_mbeanServer);
resetServerRetries();
}
@Override
protected boolean executeAndChangeState(Object message)
{
boolean success = true;
if (message instanceof ServerSetChangeMessage)
{
ServerSetChangeMessage serverSetChangeMsg = (ServerSetChangeMessage)message;
switch (serverSetChangeMsg.getTypeId())
{
case SET_SERVERS: doSetServers(serverSetChangeMsg); break;
case ADD_SERVER: doAddServer(serverSetChangeMsg); break;
case REMOVE_SERVER: doRemoveServer(serverSetChangeMsg); break;
default:
{
_log.error("Unkown ServerSetChangeMessage in ServerPullThread: " + serverSetChangeMsg.getTypeId());
success = false;
break;
}
}
if ( success && (_componentStatus.getStatus() == Status.SUSPENDED_ON_ERROR))
{
enqueueMessage(LifecycleMessage.createResumeMessage());
}
} else {
return super.executeAndChangeState(message);
}
return success;
}
private void doRemoveServer(ServerSetChangeMessage serverSetChangeMsg)
{
ServerInfo newServer = serverSetChangeMsg.getServer();
_log.info("About to remove Server (" + newServer + ") from Server set. Current Server set is :" + _servers);
if (null == newServer)
{
_log.error("No Server to remove");
return;
}
if (! _servers.contains(newServer))
{
_log.warn("Trying to remove a Server that does not exist:" + newServer.toString());
}
else
{
_log.info("Removing Server: " + newServer.toString());
Iterator<ServerInfo> iter = _servers.iterator();
int index = 0;
for (; iter.hasNext(); ++index)
{
if(newServer.equals(iter.next()))
break;
}
if ( index < _curServerIdx )
{
_curServerIdx--;
} else if ( index == _curServerIdx) {
_log.info("Trying to remove the active Server !!");
handleServerSwitch();
_curServerIdx = -1;
_curServer = null;
}
_servers.remove(newServer);
}
}
/*
* Updates the server set.
*
* Logic: Compares the existing set and new set of servers.
* If the currentServer ( connected Server) is available in the new set, then
* connection is retained and
* currentServerIdx is set such that it is pointing to the connected Server in the new set
* else
* connection is closed and
* currentServerIdx is set to -1
*
*/
protected void doSetServers(ServerSetChangeMessage serverSetChangeMsg)
{
Set<ServerInfo> ServerSet =
(null == serverSetChangeMsg.getServerSet()) ? null : new TreeSet<ServerInfo>(serverSetChangeMsg.getServerSet());
if ( (ServerSet != null ) && (_servers != null) && _servers.equals(ServerSet))
{
_log.info("doSetServers : Both old set and new set is same. Skipping this message. ServerSet is :" + ServerSet);
return;
}
boolean tearConnection = (_curServer != null)
&& ((null == ServerSet) || ( ! ServerSet.contains(_curServer)));
_log.info("About to change Server set. Old Server set was :" + _servers + ", New Server Set is :" + ServerSet);
_servers.clear();
if ( tearConnection)
{
handleServerSwitch();
if (null != ServerSet)
{
_servers.addAll(ServerSet);
}
} else {
_servers.addAll(ServerSet);
if ( _curServer != null)
{
// Set the index correctly
Iterator<ServerInfo> iter = ServerSet.iterator();
int index = 0;
for (; iter.hasNext(); ++index)
{
if(_curServer.equals(iter.next()))
break;
}
_curServerIdx = index;
} else {
_curServerIdx = -1;
_curServer = null;
}
}
resetServerRetries();
}
private void doAddServer(ServerSetChangeMessage serverSetChangeMsg)
{
ServerInfo newServer = serverSetChangeMsg.getServer();
_log.info("About to add new Server (" + newServer + ") to Server set. Current Server set is :" + _servers);
if (null == newServer)
{
_log.error("No new Server to add");
return;
}
if (_servers.contains(newServer))
{
_log.warn("Server already exists:" + newServer.toString() + " Skipping this addition !!");
}
else
{
_log.info("Adding new Server: " + newServer.toString());
_servers.add(newServer);
}
resetServerRetries();
}
protected void resetServerRetries()
{
_status.resume();
}
@Override
public void shutdown()
{
if (_statusMbean != null)
{
_statusMbean.unregisterMbean(_mbeanServer);
_log.info("mbean unregistered");
}
super.shutdown();
}
protected void backoffOnPullError()
{
if (_status.isRunningStatus()) _status.retryOnError("pull error");
else _status.retryOnLastError();
}
@Override
protected boolean shouldRetainMessageOnPause(Object msg)
{
if (msg instanceof ServerSetChangeMessage)
return true;
return super.shouldRetainMessageOnPause(msg);
}
@Override
protected boolean shouldRetainMessageOnSuspend(Object msg)
{
if (msg instanceof ServerSetChangeMessage)
return true;
return super.shouldRetainMessageOnPause(msg);
}
/*
* Allow subclasses to decide whether to tear active connection now or after getting
* response to an outstanding request (or after bootstrap).
*/
protected abstract boolean shouldDelayTearConnection(StateId stateId);
/*
* Allow subclass to close the server connection (relay/bootstrap-server)
*/
protected abstract void resetConnection();
/**
*
* Tear Connection now or set up for tearing later
*
* Contract:
* a) Subclass decides when to tear the active connection (tear now or later)
* b) Subclass closes the existing connection
* c) If the Component status is not suspended or Paused, switch to Pick_server state.
*
* If the Component status is suspended_on_error, then resume() message is added for all ServerSetChange
* Messages whether it affects the current connection or not.
*
*/
protected void handleServerSwitch()
{
boolean delayTear = shouldDelayTearConnection(_currentState.getStateId());
if ( ! delayTear )
{
tearConnection();
Status currStatus = _status.getStatus();
/*
* If it user - paused, then dont resume
* If not started, dont transition to pickServer (common to BootstrapPullThread)
*/
if ((currStatus != Status.PAUSED) &&
(currStatus != Status.SUSPENDED_ON_ERROR) &&
(_currentState.getStateId() != StateId.INITIAL))
{
enqueuePickServer(_currentState);
}
} else {
tearConnAfterResponse = true;
}
}
/*
* Tear Connection and reset member variables associated with it
*/
protected void resetConnectionAndSetFlag()
{
resetConnection();
tearConnAfterResponse = false;
}
protected void killConnection()
{
_currentState.getRelayConnection().close();
}
/*
* Tear Connection and reset member variables associated with it
*/
protected void tearConnection()
{
resetConnectionAndSetFlag();
_curServer = null;
_curServerIdx = -1;
}
protected void tearConnectionAndEnqueuePickServer()
{
tearConnection();
enqueuePickServer(_currentState);
}
protected boolean toTearConnAfterHandlingResponse()
{
return tearConnAfterResponse;
}
/**
* Clears the message queue of any pending ConnectionState Messages and enqueues the PickServer message
*/
protected void enqueuePickServer(ConnectionState connState)
{
connState.switchToPickServer();
enqueueMessageAfterFilter(connState, pickServerFilter);
}
/**
* Used when enqueing pickServer state as a result of Server-Set CHange.
* This filter ensures that message queue does not contain any more ConnectionState message before the PickServer
* message.
*/
private class PickServerEnqueueFilter implements MessageQueueFilter
{
@Override
public boolean shouldRetain(Object msg)
{
return shouldRetainMessageOnPause(msg);
}
}
public Set<ServerInfo> getServers()
{
return _servers;
}
public int getCurrentServerIdx()
{
return _curServerIdx;
}
public ServerInfo getCurentServer()
{
return _curServer;
}
public ConnectionState getConnectionState()
{
return _currentState;
}
public DatabusSourcesConnection getSourcesConnection()
{
return _sourcesConn;
}
public void setSourcesConnection(DatabusSourcesConnection conn)
{
_sourcesConn = conn;
}
@Override
protected Object preEnqueue(Object message)
{
Object ret = message;
if ( message instanceof ConnectionState)
{
ConnectionState state = (ConnectionState)message;
ConnectionStateMessage stateMsg = new ConnectionStateMessage(state.getStateId(), state);
ret = stateMsg;
}
return ret;
}
/**
* @return the log
*/
protected Logger getLog()
{
return _log;
}
protected DbusEventFactory getEventFactory()
{
return _eventFactory;
}
protected static void sendHeartbeat(UnifiedClientStats unifiedClientStats)
{
sendHeartbeat(unifiedClientStats, System.currentTimeMillis());
}
protected static void sendHeartbeat(UnifiedClientStats unifiedClientStats, long timestampMs)
{
if (unifiedClientStats != null)
{
unifiedClientStats.setHeartbeatTimestamp(timestampMs);
}
}
}