/*
* 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.server.transport;
import org.apache.avro.generic.GenericRecord;
import org.kaaproject.kaa.common.avro.GenericAvroConverter;
import org.kaaproject.kaa.server.common.zk.gen.VersionConnectionInfoPair;
import org.kaaproject.kaa.server.transport.message.MessageHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.PublicKey;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
/**
* Responsible for a classpath scan and initialization of {@link Transport} implementations.
* Provides a capability to add/remove listeners on the transports start-up events.
*
* @author Andrew Shvayka
*/
public abstract class AbstractTransportService implements TransportService {
protected static final String TRANSPORT_CONFIGURATION_SCAN_PACKAGE =
"org.kaaproject.kaa.server.transport";
/**
* The Constant LOG.
*/
private static final Logger LOG = LoggerFactory.getLogger(AbstractTransportService.class);
private final Map<Integer, TransportConfig> configs;
private final Map<Integer, Transport> transports;
private final Set<TransportUpdateListener> listeners;
/**
* Create new instance of <code>AbstractTransportService</code>.
*/
public AbstractTransportService() {
super();
this.configs = new HashMap<Integer, TransportConfig>();
this.transports = new HashMap<Integer, Transport>();
this.listeners = new HashSet<TransportUpdateListener>();
}
private static List<org.kaaproject.kaa.server.common.zk.gen.TransportMetaData> toTransportMdList(
Map<Integer, Transport> transportMap) {
List<org.kaaproject.kaa.server.common.zk.gen.TransportMetaData> mdList =
new ArrayList<>(transportMap.size());
for (Entry<Integer, Transport> entry : transportMap.entrySet()) {
TransportMetaData source = entry.getValue().getConnectionInfo();
org.kaaproject.kaa.server.common.zk.gen.TransportMetaData md =
new org.kaaproject.kaa.server.common.zk.gen.TransportMetaData();
md.setId(entry.getKey());
md.setMinSupportedVersion(source.getMinSupportedVersion());
md.setMaxSupportedVersion(source.getMaxSupportedVersion());
List<VersionConnectionInfoPair> connectionInfoList = new ArrayList<>();
for (int i = md.getMinSupportedVersion(); i <= md.getMaxSupportedVersion(); i++) {
for (byte[] connectionInfo : source.getConnectionInfoList(i)) {
connectionInfoList.add(new VersionConnectionInfoPair(i, ByteBuffer.wrap(connectionInfo)));
}
}
md.setConnectionInfo(connectionInfoList);
mdList.add(md);
}
return mdList;
}
@Override
public void lookupAndInit() {
LOG.info("Lookup of available transport configurations started in package {}.",
TRANSPORT_CONFIGURATION_SCAN_PACKAGE);
configs.clear();
transports.clear();
ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(KaaTransportConfig.class));
Set<BeanDefinition> beans = scanner.findCandidateComponents(
TRANSPORT_CONFIGURATION_SCAN_PACKAGE);
for (BeanDefinition bean : beans) {
LOG.info("Found transport configuration {}", bean.getBeanClassName());
try {
Class<?> clazz = Class.forName(bean.getBeanClassName());
TransportConfig transportConfig = (TransportConfig) clazz.newInstance();
configs.put(transportConfig.getId(), transportConfig);
} catch (ReflectiveOperationException exception) {
LOG.error(MessageFormat.format("Failed to init transport configuration for {0}",
bean.getBeanClassName()), exception);
}
}
LOG.info("Lookup of available transport configurations found {} configurations.",
configs.size());
LOG.info("Lookup of transport properties started");
TransportProperties transportProperties = new TransportProperties(getServiceProperties());
LOG.info("Lookup of transport properties found {} properties", transportProperties.size());
for (TransportConfig config : configs.values()) {
LOG.info("Initializing transport with name {} and class {}",
config.getName(), config.getTransportClass());
try {
Class<?> clazz = Class.forName(config.getTransportClass());
Transport transport = (Transport) clazz.newInstance();
String transportConfigFile = getTransportConfigPrefix() + "-" + config.getConfigFileName();
LOG.info("Lookup of transport configuration file {}", transportConfigFile);
URL configFileUrl = this.getClass().getClassLoader().getResource(transportConfigFile);
GenericAvroConverter<GenericRecord> configConverter =
new GenericAvroConverter<>(config.getConfigSchema());
GenericRecord configRecord = configConverter.decodeJson(
Files.readAllBytes(Paths.get(configFileUrl.toURI())));
LOG.info("Lookup of transport configuration file {}", transportConfigFile);
TransportContext context = new TransportContext(
transportProperties, getPublicKey(), getMessageHandler());
transport.init(new GenericTransportContext(context, configConverter.encode(configRecord)));
transports.put(config.getId(), transport);
} catch (ReflectiveOperationException | IOException
| URISyntaxException | TransportLifecycleException exception) {
LOG.error(MessageFormat.format("Failed to init transport for {0}",
config.getTransportClass()), exception);
}
}
}
@Override
public void start() {
LOG.info("Starting {} available transports.", transports.size());
for (Entry<Integer, Transport> entry : transports.entrySet()) {
LOG.info("Starting transport {}.", configs.get(entry.getKey()).getName());
entry.getValue().start();
}
notifyListeners();
}
@Override
public void stop() {
LOG.info("Stoping {} available transports.", transports.size());
for (Entry<Integer, Transport> entry : transports.entrySet()) {
LOG.info("Stoping transport {}.", configs.get(entry.getKey()).getName());
entry.getValue().stop();
}
}
@Override
public boolean addListener(TransportUpdateListener listener) {
LOG.info("Adding transport update listener {}.", listener);
return listeners.add(listener);
}
@Override
public boolean removeListener(TransportUpdateListener listener) {
LOG.info("Removing transport update listener {}.", listener);
return listeners.remove(listener);
}
protected abstract String getTransportConfigPrefix();
protected abstract Properties getServiceProperties();
protected abstract MessageHandler getMessageHandler();
protected abstract PublicKey getPublicKey();
private void notifyListeners() {
List<org.kaaproject.kaa.server.common.zk.gen.TransportMetaData> mdList = toTransportMdList(
transports);
for (TransportUpdateListener listener : listeners) {
listener.onTransportsStarted(mdList);
}
}
}