/*
* Copyright 2014-2016 CyberVision, Inc.
*
* 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.
*/
package org.kaaproject.kaa.client.bootstrap;
import org.kaaproject.kaa.client.FailureListener;
import org.kaaproject.kaa.client.channel.BootstrapTransport;
import org.kaaproject.kaa.client.channel.GenericTransportInfo;
import org.kaaproject.kaa.client.channel.KaaInternalChannelManager;
import org.kaaproject.kaa.client.channel.ServerType;
import org.kaaproject.kaa.client.channel.TransportConnectionInfo;
import org.kaaproject.kaa.client.channel.TransportProtocolId;
import org.kaaproject.kaa.client.channel.failover.FailoverDecision;
import org.kaaproject.kaa.client.channel.failover.FailoverManager;
import org.kaaproject.kaa.client.channel.failover.FailoverStatus;
import org.kaaproject.kaa.client.context.ExecutorContext;
import org.kaaproject.kaa.client.transport.TransportException;
import org.kaaproject.kaa.common.TransportType;
import org.kaaproject.kaa.common.endpoint.gen.ProtocolMetaData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* Default {@link BootstrapManager} implementation.
*
* @author Yaroslav Zeygerman
*/
public class DefaultBootstrapManager implements BootstrapManager {
/**
* The Constant LOG.
*/
private static final Logger LOG = LoggerFactory.getLogger(DefaultBootstrapManager.class);
private final Map<TransportProtocolId, List<ProtocolMetaData>> mappedOperationServerList =
new HashMap<>();
private final Map<TransportProtocolId, Iterator<ProtocolMetaData>> mappedIterators =
new HashMap<>();
private FailureListener failureListener;
private BootstrapTransport transport;
private List<ProtocolMetaData> operationsServerList;
private KaaInternalChannelManager channelManager;
private FailoverManager failoverManager;
private Integer serverToApply;
private ExecutorContext executorContext;
/**
* All-args constructor.
*/
public DefaultBootstrapManager(BootstrapTransport transport, ExecutorContext executorContext,
FailureListener failureListener) {
this.transport = transport;
this.executorContext = executorContext;
this.failureListener = failureListener;
}
@Override
public void receiveOperationsServerList() throws TransportException {
LOG.debug("Going to invoke sync method of assigned transport");
transport.sync();
}
@Override
public void useNextOperationsServer(TransportProtocolId transportId, FailoverStatus status) {
if (mappedOperationServerList != null && !mappedOperationServerList.isEmpty()) {
if (mappedIterators.get(transportId).hasNext()) {
ProtocolMetaData nextOperationsServer = mappedIterators.get(transportId).next();
LOG.debug("New server [{}] will be user for [{}]",
nextOperationsServer.getAccessPointId(), transportId);
if (channelManager != null) {
channelManager.onTransportConnectionInfoUpdated(
new GenericTransportInfo(ServerType.OPERATIONS, nextOperationsServer));
} else {
LOG.error("Can not process server change. Channel manager was not specified");
}
} else {
LOG.warn("Failed to find server for channel [{}]", transportId);
resolveFailoverStatus(status);
}
} else {
throw new BootstrapRuntimeException("Operations Server list is empty");
}
}
@Override
public synchronized void setTransport(BootstrapTransport transport) {
this.transport = transport;
}
@Override
public synchronized void useNextOperationsServerByAccessPointId(int accessPointId) {
List<ProtocolMetaData> servers = getTransportsByAccessPointId(accessPointId);
if (servers != null && !servers.isEmpty()) {
notifyChannelManagerAboutServer(servers);
} else {
serverToApply = accessPointId;
transport.sync();
}
}
private void notifyChannelManagerAboutServer(List<ProtocolMetaData> transports) {
for (ProtocolMetaData transport : transports) {
LOG.debug("Applying new transport {}", transports);
channelManager.onTransportConnectionInfoUpdated(new GenericTransportInfo(
ServerType.OPERATIONS, transport));
}
}
private List<ProtocolMetaData> getTransportsByAccessPointId(int accessPointId) {
if (operationsServerList == null || operationsServerList.isEmpty()) {
throw new BootstrapRuntimeException("Operations Server list is empty");
}
List<ProtocolMetaData> result = new ArrayList<>();
for (ProtocolMetaData transport : operationsServerList) {
if (transport.getAccessPointId().intValue() == accessPointId) {
result.add(transport);
}
}
return result;
}
@Override
public synchronized void setChannelManager(KaaInternalChannelManager manager) {
this.channelManager = manager;
}
@Override
public synchronized void setFailoverManager(FailoverManager failoverManager) {
this.failoverManager = failoverManager;
}
@Override
public synchronized void onProtocolListUpdated(List<ProtocolMetaData> list) {
LOG.trace("Protocol list was updated");
operationsServerList = list;
mappedOperationServerList.clear();
mappedIterators.clear();
if (operationsServerList == null || operationsServerList.isEmpty()) {
LOG.trace("Received empty operations service list");
resolveFailoverStatus(FailoverStatus.NO_OPERATION_SERVERS_RECEIVED);
return;
}
for (ProtocolMetaData server : operationsServerList) {
TransportProtocolId transportId = new TransportProtocolId(
server.getProtocolVersionInfo().getId(),
server.getProtocolVersionInfo().getVersion());
List<ProtocolMetaData> servers = mappedOperationServerList.get(transportId);
if (servers == null) {
servers = new LinkedList<>();
mappedOperationServerList.put(transportId, servers);
}
servers.add(server);
}
for (Map.Entry<TransportProtocolId, List<ProtocolMetaData>> entry : mappedOperationServerList
.entrySet()) {
Collections.shuffle(entry.getValue());
mappedIterators.put(entry.getKey(), entry.getValue().iterator());
}
if (serverToApply != null) {
List<ProtocolMetaData> servers = getTransportsByAccessPointId(serverToApply);
if (servers != null && !servers.isEmpty()) {
notifyChannelManagerAboutServer(servers);
serverToApply = null;
}
} else {
for (Map.Entry<TransportProtocolId, Iterator<ProtocolMetaData>> entry : mappedIterators
.entrySet()) {
TransportConnectionInfo info = new GenericTransportInfo(ServerType.OPERATIONS,
entry.getValue().next());
channelManager.onTransportConnectionInfoUpdated(info);
}
}
}
private void resolveFailoverStatus(FailoverStatus status) {
FailoverDecision decision = failoverManager.onFailover(status);
switch (decision.getAction()) {
case NOOP:
LOG.warn("No operation is performed according to failover strategy decision");
break;
case RETRY:
long retryPeriod = decision.getRetryPeriod();
LOG.warn("Attempt to receive operations service list will be made in {} ms, "
+ "according to failover strategy decision", retryPeriod);
executorContext.getScheduledExecutor().schedule(new Runnable() {
@Override
public void run() {
try {
receiveOperationsServerList();
} catch (TransportException ex) {
LOG.error("Error while receiving operations service list", ex);
}
}
}, retryPeriod, TimeUnit.MILLISECONDS);
break;
case USE_NEXT_BOOTSTRAP:
LOG.warn("Trying to switch to the next bootstrap service according to failover strategy "
+ "decision");
retryPeriod = decision.getRetryPeriod();
failoverManager.onServerFailed(channelManager.getActiveServer(TransportType.BOOTSTRAP),
status);
executorContext.getScheduledExecutor().schedule(new Runnable() {
@Override
public void run() {
try {
receiveOperationsServerList();
} catch (TransportException ex) {
LOG.error("Error while receiving operations service list", ex);
}
}
}, retryPeriod, TimeUnit.MILLISECONDS);
break;
case FAILURE:
LOG.warn("Calling failure listener according to failover strategy decision!");
failureListener.onFailure();
break;
default:
break;
}
}
}