/*
* 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.channel.impl.channels;
import org.kaaproject.kaa.client.AbstractKaaClient;
import org.kaaproject.kaa.client.channel.ChannelDirection;
import org.kaaproject.kaa.client.channel.IpTransportInfo;
import org.kaaproject.kaa.client.channel.KaaDataChannel;
import org.kaaproject.kaa.client.channel.KaaDataDemultiplexer;
import org.kaaproject.kaa.client.channel.KaaDataMultiplexer;
import org.kaaproject.kaa.client.channel.TransportConnectionInfo;
import org.kaaproject.kaa.client.channel.TransportProtocolId;
import org.kaaproject.kaa.client.channel.TransportProtocolIdConstants;
import org.kaaproject.kaa.client.channel.connectivity.ConnectivityChecker;
import org.kaaproject.kaa.client.channel.failover.FailoverManager;
import org.kaaproject.kaa.client.channel.failover.FailoverStatus;
import org.kaaproject.kaa.client.persistence.KaaClientState;
import org.kaaproject.kaa.client.transport.AbstractHttpClient;
import org.kaaproject.kaa.common.TransportType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public abstract class AbstractHttpChannel implements KaaDataChannel {
public static final Logger LOG = LoggerFactory // NOSONAR
.getLogger(AbstractHttpChannel.class);
private static final int UNAUTHORIZED_HTTP_STATUS = 401;
private static final int FORBIDDEN_HTTP_STATUS = 403;
private final AbstractKaaClient client;
private final KaaClientState state;
private final FailoverManager failoverManager;
private IpTransportInfo currentServer;
private volatile ExecutorService executor;
private volatile boolean lastConnectionFailed = false;
private volatile boolean isShutdown = false;
private volatile boolean isPaused = false;
private AbstractHttpClient httpClient;
private KaaDataDemultiplexer demultiplexer;
private KaaDataMultiplexer multiplexer;
/**
* All-args constructor.
*/
public AbstractHttpChannel(AbstractKaaClient client, KaaClientState state,
FailoverManager failoverManager) {
this.client = client;
this.state = state;
this.failoverManager = failoverManager;
}
protected ExecutorService createExecutor() {
LOG.info("Creating a new executor for channel {}", getId());
return Executors.newSingleThreadExecutor();
}
@Override
public TransportProtocolId getTransportProtocolId() {
return TransportProtocolIdConstants.HTTP_TRANSPORT_ID;
}
@Override
public synchronized void sync(TransportType type) {
sync(Collections.singleton(type));
}
@Override
public synchronized void sync(Set<TransportType> types) {
if (isShutdown) {
LOG.info("Can't sync. Channel {} is down", getId());
return;
}
if (isPaused) {
LOG.info("Can't sync. Channel {} is paused", getId());
return;
}
if (multiplexer == null) {
LOG.warn("Can't sync. Channel {} multiplexer is not set", getId());
return;
}
if (demultiplexer == null) {
LOG.warn("Can't sync. Channel {} demultiplexer is not set", getId());
return;
}
if (currentServer == null) {
lastConnectionFailed = true;
LOG.warn("Can't sync. Server is null");
}
Map<TransportType, ChannelDirection> typeMap = new HashMap<>(1);
for (TransportType type : types) {
LOG.info("Processing sync {} for channel {}", type, getId());
ChannelDirection direction = getSupportedTransportTypes().get(type);
if (direction != null) {
typeMap.put(type, direction);
} else {
LOG.error("Unsupported type {} for channel {}", type, getId());
}
}
executor.submit(createChannelRunnable(typeMap));
}
@Override
public synchronized void syncAll() {
if (isShutdown) {
LOG.info("Can't sync. Channel {} is down", getId());
return;
}
if (isPaused) {
LOG.info("Can't sync. Channel {} is paused", getId());
return;
}
LOG.info("Processing sync all for channel {}", getId());
if (multiplexer != null && demultiplexer != null) {
if (currentServer != null) {
executor.submit(createChannelRunnable(getSupportedTransportTypes()));
} else {
lastConnectionFailed = true;
LOG.warn("Can't sync. Server is null");
}
}
}
@Override
public void syncAck(TransportType type) {
syncAck(Collections.singleton(type));
}
@Override
public void syncAck(Set<TransportType> types) {
LOG.info("Sync ack message is ignored for Channel {}", getId());
}
protected abstract String getUrlSufix();
@Override
public TransportConnectionInfo getServer() {
return currentServer;
}
@Override
public synchronized void setServer(TransportConnectionInfo server) {
if (isShutdown) {
LOG.info("Can't set server. Channel {} is down", getId());
return;
}
if (executor == null && !isPaused) {
executor = createExecutor();
}
if (server != null) {
this.currentServer = new IpTransportInfo(server);
this.httpClient = client.createHttpClient(currentServer.getUrl() + getUrlSufix(),
state.getPrivateKey(), state.getPublicKey(),
currentServer.getPublicKey());
if (lastConnectionFailed && !isPaused) {
lastConnectionFailed = false;
syncAll();
}
}
}
@Override
public void setConnectivityChecker(ConnectivityChecker checker) {
}
@Override
public void shutdown() {
if (!isShutdown) {
isShutdown = true;
if (executor != null) {
executor.shutdownNow();
}
}
}
@Override
public void pause() {
if (isShutdown) {
LOG.info("Can't pause channel. Channel [{}] is down", getId());
return;
}
if (!isPaused) {
isPaused = true;
if (executor != null) {
executor.shutdownNow();
executor = null;
}
}
}
@Override
public void resume() {
if (isShutdown) {
LOG.info("Can't resume channel. Channel [{}] is down", getId());
return;
}
if (isPaused) {
isPaused = false;
if (executor == null) {
executor = createExecutor();
}
if (lastConnectionFailed) {
lastConnectionFailed = false;
syncAll();
}
}
}
protected void connectionFailed(boolean failed) {
connectionFailed(failed, -1);
}
protected void connectionFailed(boolean failed, int status) {
FailoverStatus failoverStatus = FailoverStatus.OPERATION_SERVERS_NA;
switch (status) {
case UNAUTHORIZED_HTTP_STATUS:
state.clean();
failoverStatus = FailoverStatus.ENDPOINT_VERIFICATION_FAILED;
break;
case FORBIDDEN_HTTP_STATUS:
failoverStatus = FailoverStatus.ENDPOINT_CREDENTIALS_REVOKED;
break;
default:
break;
}
lastConnectionFailed = failed;
if (failed) {
failoverManager.onServerFailed(currentServer, failoverStatus);
} else {
failoverManager.onServerConnected(currentServer);
}
}
protected KaaDataMultiplexer getMultiplexer() {
return multiplexer;
}
@Override
public synchronized void setMultiplexer(KaaDataMultiplexer multiplexer) {
if (multiplexer != null) {
this.multiplexer = multiplexer;
}
}
protected KaaDataDemultiplexer getDemultiplexer() {
return demultiplexer;
}
@Override
public synchronized void setDemultiplexer(KaaDataDemultiplexer demultiplexer) {
if (demultiplexer != null) {
this.demultiplexer = demultiplexer;
}
}
protected AbstractHttpClient getHttpClient() {
return httpClient;
}
protected abstract Runnable createChannelRunnable(Map<TransportType, ChannelDirection> typeMap);
public boolean isShutdown() {
return isShutdown;
}
}