/*
* 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.control.service.sdk;
import org.apache.avro.Schema;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveOutputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.compressors.CompressorOutputStream;
import org.apache.commons.compress.compressors.CompressorStreamFactory;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.kaaproject.kaa.common.dto.admin.SdkProfileDto;
import org.kaaproject.kaa.common.dto.file.FileData;
import org.kaaproject.kaa.server.common.Environment;
import org.kaaproject.kaa.server.common.Version;
import org.kaaproject.kaa.server.common.zk.ServerNameUtil;
import org.kaaproject.kaa.server.common.zk.gen.BootstrapNodeInfo;
import org.kaaproject.kaa.server.common.zk.gen.TransportMetaData;
import org.kaaproject.kaa.server.common.zk.gen.VersionConnectionInfoPair;
import org.kaaproject.kaa.server.control.service.sdk.compress.TarEntryData;
import org.kaaproject.kaa.server.control.service.sdk.event.CppEventSourcesGenerator;
import org.kaaproject.kaa.server.control.service.sdk.event.EventFamilyMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* The Class CppSdkGenerator.
*/
public class CppSdkGenerator extends SdkGenerator {
private static final Logger LOG = LoggerFactory.getLogger(CppSdkGenerator.class);
private static final String CPP_SDK_DIR = "sdk/cpp";
private static final String CPP_SDK_PREFIX = "kaa-cpp-ep-sdk-";
private static final String SDK_VAR = "%{application.sdk_token}";
private static final String SDK_PROFILE_VERSION_VAR = "%{application.profile_version}";
private static final String CLIENT_PUB_KEY_LOCATION_VAR = "%{application.public_key_location}";
private static final String CLIENT_PRIV_KEY_LOCATION_VAR = "%{application.private_key_location}";
private static final String CLIENT_STATUS_FILE_LOCATION_VAR =
"%{application.status_file_location}";
private static final String POLLING_PERIOD_SECONDS_VAR = "%{application.polling_period_seconds}";
private static final String BOOTSTRAP_SERVERS_INFO_VAR = "%{bootstrap_servers_info}";
private static final String CONFIG_DATA_DEFAULT_VAR = "%{config.data.default}";
private static final String BUILD_VERSION = "%{build.version}";
private static final String BUILD_COMMIT_HASH = "%{build.commit_hash}";
private static final String DEFAULT_USER_VERIFIER_TOKEN = "%{user_verifier_token}";
private static final String SDK_DEFAULTS_TEMPLATE = "sdk/cpp/KaaDefaults.cpp.template";
private static final String SDK_DEFAULTS_PATH = "impl/KaaDefaults.cpp";
private static final String RECORD_CLASS_NAME_VAR = "%{record_class_name}";
private static final String PROFILE_SCHEMA_AVRO_SRC = "avro/profile.avsc";
private static final String PROFILE_DEFINITIONS_TEMPLATE =
"sdk/cpp/profile/ProfileDefinitions.hpp.template";
private static final String PROFILE_DEFINITIONS_PATH = "kaa/profile/gen/ProfileDefinitions.hpp";
private static final String LOG_SCHEMA_AVRO_SRC = "avro/log.avsc";
private static final String LOG_DEFINITIONS_TEMPLATE = "sdk/cpp/log/LogDefinitions.hpp.template";
private static final String LOG_DEFINITIONS_PATH = "kaa/log/gen/LogDefinitions.hpp";
private static final String NOTIFICATION_SCHEMA_AVRO_SRC = "avro/notification.avsc";
private static final String NOTIFICATION_DEFINITIONS_TEMPLATE =
"sdk/cpp/notification/NotificationDefinitions.hpp.template";
private static final String NOTIFICATION_DEFINITIONS_PATH =
"kaa/notification/gen/NotificationDefinitions.hpp";
private static final String CONFIGURATION_SCHEMA_AVRO_SRC = "avro/configuration.avsc";
private static final String CONFIGURATION_DEFINITIONS_TEMPLATE =
"sdk/cpp/configuration/ConfigurationDefinitions.hpp.template";
private static final String CONFIGURATION_DEFINITIONS_PATH =
"kaa/configuration/gen/ConfigurationDefinitions.hpp";
/**
* Replace string.
*
* @param body the body
* @param variable the variable
* @param value the value
* @return the string
*/
private static String replaceVar(String body, String variable, String value) {
return body.replace(variable, value);
}
/* (non-Javadoc)
* @see org.kaaproject.kaa.server.control.service.sdk.SdkGenerator#generateSdk(
* java.lang.String, java.util.List,
* java.lang.String, int,
* int, int,
* java.lang.String, java.lang.String,
* java.lang.String, byte[], java.util.List)
*/
@Override
public FileData generateSdk(String buildVersion,
List<BootstrapNodeInfo> bootstrapNodes,
SdkProfileDto sdkProfile,
String profileSchemaBody,
String notificationSchemaBody,
String configurationProtocolSchemaBody,
String configurationBaseSchema,
byte[] defaultConfigurationData,
List<EventFamilyMetadata> eventFamilies,
String logSchemaBody) throws Exception {
String sdkToken = sdkProfile.getToken();
Integer configurationSchemaVersion = sdkProfile.getConfigurationSchemaVersion();
final Integer profileSchemaVersion = sdkProfile.getProfileSchemaVersion();
Integer notificationSchemaVersion = sdkProfile.getNotificationSchemaVersion();
Integer logSchemaVersion = sdkProfile.getLogSchemaVersion();
String defaultVerifierToken = sdkProfile.getDefaultVerifierToken();
String sdkTemplateLocation = Environment.getServerHomeDir() + "/" + CPP_SDK_DIR
+ "/" + CPP_SDK_PREFIX + buildVersion + ".tar.gz";
LOG.debug("Lookup Java SDK template: {}", sdkTemplateLocation);
CompressorStreamFactory csf = new CompressorStreamFactory();
ArchiveStreamFactory asf = new ArchiveStreamFactory();
CompressorInputStream cis = csf.createCompressorInputStream(CompressorStreamFactory.GZIP,
new FileInputStream(sdkTemplateLocation));
final ArchiveInputStream templateArchive = asf.createArchiveInputStream(
ArchiveStreamFactory.TAR, cis
);
ByteArrayOutputStream sdkOutput = new ByteArrayOutputStream();
CompressorOutputStream cos = csf.createCompressorOutputStream(CompressorStreamFactory.GZIP,
sdkOutput);
ArchiveOutputStream sdkFile = asf.createArchiveOutputStream(ArchiveStreamFactory.TAR, cos);
Map<String, TarEntryData> replacementData = new HashMap<>();
List<TarEntryData> cppSources = new ArrayList<>();
// TODO: remove all version fields and add single sdkToken field
// create entry for default properties
TarArchiveEntry entry = new TarArchiveEntry(SDK_DEFAULTS_PATH);
byte[] data = generateKaaDefaults(bootstrapNodes, sdkToken, defaultConfigurationData,
defaultVerifierToken);
entry.setSize(data.length);
TarEntryData tarEntry = new TarEntryData(entry, data);
cppSources.add(tarEntry);
Map<String, String> profileVars = new HashMap<>();
profileVars.put(SDK_PROFILE_VERSION_VAR, profileSchemaVersion.toString());
cppSources.addAll(processFeatureSchema(profileSchemaBody, PROFILE_SCHEMA_AVRO_SRC,
PROFILE_DEFINITIONS_TEMPLATE, PROFILE_DEFINITIONS_PATH, profileVars));
cppSources.addAll(processFeatureSchema(notificationSchemaBody, NOTIFICATION_SCHEMA_AVRO_SRC,
NOTIFICATION_DEFINITIONS_TEMPLATE, NOTIFICATION_DEFINITIONS_PATH, null));
cppSources.addAll(processFeatureSchema(logSchemaBody, LOG_SCHEMA_AVRO_SRC,
LOG_DEFINITIONS_TEMPLATE, LOG_DEFINITIONS_PATH, null));
cppSources.addAll(processFeatureSchema(configurationBaseSchema, CONFIGURATION_SCHEMA_AVRO_SRC,
CONFIGURATION_DEFINITIONS_TEMPLATE, CONFIGURATION_DEFINITIONS_PATH, null));
if (eventFamilies != null && !eventFamilies.isEmpty()) {
cppSources.addAll(CppEventSourcesGenerator.generateEventSources(eventFamilies));
}
for (TarEntryData entryData : cppSources) {
replacementData.put(entryData.getEntry().getName(), entryData);
}
ArchiveEntry archiveEntry;
while ((archiveEntry = templateArchive.getNextEntry()) != null) {
if (!archiveEntry.isDirectory()) {
if (replacementData.containsKey(archiveEntry.getName())) {
TarEntryData entryData = replacementData.remove(archiveEntry.getName());
sdkFile.putArchiveEntry(entryData.getEntry());
sdkFile.write(entryData.getData());
} else {
sdkFile.putArchiveEntry(archiveEntry);
IOUtils.copy(templateArchive, sdkFile);
}
} else {
sdkFile.putArchiveEntry(archiveEntry);
}
sdkFile.closeArchiveEntry();
}
templateArchive.close();
for (String entryName : replacementData.keySet()) {
TarEntryData entryData = replacementData.get(entryName);
sdkFile.putArchiveEntry(entryData.getEntry());
sdkFile.write(entryData.getData());
sdkFile.closeArchiveEntry();
}
sdkFile.finish();
sdkFile.close();
String sdkFileName = CPP_SDK_PREFIX + sdkProfile.getToken() + ".tar.gz";
byte[] sdkData = sdkOutput.toByteArray();
FileData sdk = new FileData();
sdk.setFileName(sdkFileName);
sdk.setFileData(sdkData);
return sdk;
}
private List<TarEntryData> processFeatureSchema(String schemaBody, String schemaPath,
String templatePath, String outputPath,
Map<String, String> vars) throws IOException {
List<TarEntryData> cppSources = new LinkedList<>();
if (!StringUtils.isBlank(schemaBody)) {
TarArchiveEntry entry = new TarArchiveEntry(schemaPath);
byte[] data = schemaBody.getBytes();
entry.setSize(data.length);
TarEntryData tarEntry = new TarEntryData(entry, data);
cppSources.add(tarEntry);
Schema schema = new Schema.Parser().parse(schemaBody);
String definitionsHpp = SdkGenerator.readResource(templatePath);
entry = new TarArchiveEntry(outputPath);
String templateStr = replaceVar(definitionsHpp, RECORD_CLASS_NAME_VAR, schema.getName());
if (vars != null && !vars.isEmpty()) {
for (Entry<String, String> var : vars.entrySet()) {
templateStr = replaceVar(templateStr, var.getKey(), var.getValue());
}
}
byte[] definitionsData = templateStr.getBytes();
entry.setSize(definitionsData.length);
tarEntry = new TarEntryData(entry, definitionsData);
cppSources.add(tarEntry);
}
return cppSources;
}
/**
* Generate client properties.
*
* @param bootstrapNodes the bootstrap nodes
* @param sdkToken the app token
* @param defaultConfigurationData the default configuration data
* @return the byte[]
* @throws IOException Signals that an I/O exception has occurred.
*/
private byte[] generateKaaDefaults(List<BootstrapNodeInfo> bootstrapNodes,
String sdkToken,
byte[] defaultConfigurationData,
String defaultVerifierToken) throws IOException {
String kaaDefaultsString = SdkGenerator.readResource(SDK_DEFAULTS_TEMPLATE);
LOG.debug("[sdk generateClientProperties] bootstrapNodes.size(): {}", bootstrapNodes.size());
kaaDefaultsString = replaceVar(kaaDefaultsString, BUILD_VERSION, Version.PROJECT_VERSION);
kaaDefaultsString = replaceVar(kaaDefaultsString, BUILD_COMMIT_HASH, Version.COMMIT_HASH);
kaaDefaultsString = replaceVar(kaaDefaultsString, SDK_VAR, sdkToken);
kaaDefaultsString = replaceVar(kaaDefaultsString, POLLING_PERIOD_SECONDS_VAR, "5");
kaaDefaultsString = replaceVar(kaaDefaultsString, CLIENT_PUB_KEY_LOCATION_VAR, "key.public");
kaaDefaultsString = replaceVar(kaaDefaultsString, CLIENT_PRIV_KEY_LOCATION_VAR, "key.private");
kaaDefaultsString = replaceVar(kaaDefaultsString, CLIENT_STATUS_FILE_LOCATION_VAR,
"kaa.status");
kaaDefaultsString = replaceVar(kaaDefaultsString,
DEFAULT_USER_VERIFIER_TOKEN,
defaultVerifierToken != null ? defaultVerifierToken : "");
String bootstrapServers = "";
LOG.debug("[sdk generateClientProperties] bootstrapNodes.size(): {}", bootstrapNodes.size());
for (BootstrapNodeInfo node : bootstrapNodes) {
List<TransportMetaData> supportedChannels = node.getTransports();
int accessPointId = ServerNameUtil.crc32(node.getConnectionInfo());
for (TransportMetaData transport : supportedChannels) {
for (VersionConnectionInfoPair pair : transport.getConnectionInfo()) {
String serverPattern = "listOfServers.push_back(createTransportInfo(";
serverPattern += "0x" + Integer.toHexString(accessPointId);
serverPattern += ", ";
serverPattern += "0x" + Integer.toHexString(transport.getId());
serverPattern += ", ";
serverPattern += pair.getVersion();
serverPattern += ", \"";
serverPattern += Base64.encodeBase64String(pair.getConenctionInfo().array());
serverPattern += "\"";
serverPattern += "));\n";
bootstrapServers += serverPattern;
}
}
}
kaaDefaultsString = replaceVar(kaaDefaultsString, BOOTSTRAP_SERVERS_INFO_VAR, bootstrapServers);
kaaDefaultsString = replaceVar(kaaDefaultsString, CONFIG_DATA_DEFAULT_VAR,
Base64.encodeBase64String(defaultConfigurationData));
return kaaDefaultsString.getBytes();
}
}