/*
* 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.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.avro.avrogen.compiler.Compiler;
import org.kaaproject.kaa.avro.avrogen.compiler.ObjectiveCCompiler;
import org.kaaproject.kaa.common.dto.admin.SdkProfileDto;
import org.kaaproject.kaa.common.dto.file.FileData;
import org.kaaproject.kaa.server.common.Version;
import org.kaaproject.kaa.server.common.zk.gen.BootstrapNodeInfo;
import org.kaaproject.kaa.server.control.service.sdk.compress.TarEntryData;
import org.kaaproject.kaa.server.control.service.sdk.event.EventFamilyMetadata;
import org.kaaproject.kaa.server.control.service.sdk.event.ObjCEventClassesGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class ObjCSdkGenerator extends SdkGenerator {
private static final Logger LOG = LoggerFactory.getLogger(ObjCSdkGenerator.class);
private static final String CONFIGURATION_DIR = "configuration/";
private static final String NOTIFICATION_DIR = "notification/";
private static final String PROFILE_DIR = "profile/";
private static final String LOG_DIR = "log/";
private static final String EVENT_DIR = "event/";
private static final String SDK_TEMPLATE_DIR = "sdk/objc/";
private static final String CONFIGURATION_TEMPLATE_DIR = SDK_TEMPLATE_DIR + CONFIGURATION_DIR;
private static final String NOTIFICATION_TEMPLATE_DIR = SDK_TEMPLATE_DIR + NOTIFICATION_DIR;
private static final String PROFILE_TEMPLATE_DIR = SDK_TEMPLATE_DIR + PROFILE_DIR;
private static final String LOG_TEMPLATE_DIR = SDK_TEMPLATE_DIR + LOG_DIR;
private static final String EVENT_TEMPLATE_DIR = SDK_TEMPLATE_DIR + EVENT_DIR;
private static final String SDK_PREFIX = "kaa-objc-ep-sdk-";
private static final String KAA_ROOT = "Kaa/";
private static final String KAA_GEN_FOLDER = KAA_ROOT + "avro/gen/";
private static final String KAA_SOURCE_PREFIX = "KAA";
private static final String TEMPLATE_SUFFIX = ".template";
private static final String _H = ".h";
private static final String _M = ".m";
private static final String NOTIFICATION_GEN = "NotificationGen";
private static final String PROFILE_GEN = "ProfileGen";
private static final String LOG_GEN = "LogGen";
private static final String CONFIGURATION_GEN = "ConfigurationGen";
private static final String KAA_DEFAULTS = "KaaDefaults.h";
private static final String KAA_CLIENT = "KaaClient.h";
private static final String BASE_KAA_CLIENT = "BaseKaaClient";
private static final String CONFIGURATION_COMMON = "ConfigurationCommon.h";
private static final String CONFIGURATION_MANAGER_IMPL = "ResyncConfigurationManager";
private static final String CONFIGURATION_DESERIALIZER = "ConfigurationDeserializer";
private static final String NOTIFICATION_COMMON = "NotificationCommon";
private static final String PROFILE_COMMON = "ProfileCommon";
private static final String LOG_RECORD = "LogRecord";
private static final String LOG_COLLECTOR_INTERFACE = "LogCollector.h";
private static final String LOG_COLLECTOR_SOURCE = "DefaultLogCollector";
private static final String USER_VERIFIER_CONSTANTS = "UserVerifierConstants.h";
private static final String PROFILE_CLASS_VAR = "\\$\\{profile_class\\}";
private static final String CONFIGURATION_CLASS_VAR = "\\$\\{configuration_class\\}";
private static final String NOTIFICATION_CLASS_VAR = "\\$\\{notification_class\\}";
private static final String LOG_RECORD_CLASS_VAR = "\\$\\{log_record_class\\}";
private static final String DEFAULT_USER_VERIFIER_TOKEN_VAR =
"\\$\\{default_user_verifier_token\\}";
private static final String PROFILE_NAMESPACE = KAA_SOURCE_PREFIX + "Profile";
private static final String LOGGING_NAMESPACE = KAA_SOURCE_PREFIX + "Logging";
private static final String CONFIGURATION_NAMESPACE = KAA_SOURCE_PREFIX + "Configuration";
private static final String NOTIFICATION_NAMESPACE = KAA_SOURCE_PREFIX + "notification";
@Override
public FileData generateSdk(String buildVersion,
List<BootstrapNodeInfo> bootstrapNodes,
SdkProfileDto sdkProperties,
String profileSchemaBody,
String notificationSchemaBody,
String configurationProtocolSchemaBody,
String configurationBaseSchemaBody,
byte[] defaultConfigurationData,
List<EventFamilyMetadata> eventFamilies,
String logSchemaBody) throws Exception {
final String sdkToken = sdkProperties.getToken();
final String defaultVerifierToken = sdkProperties.getDefaultVerifierToken();
String sdkTemplateLocation = System.getProperty("server_home_dir")
+ "/" + SDK_TEMPLATE_DIR + SDK_PREFIX + buildVersion + ".tar.gz";
LOG.debug("Lookup Objective C 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> objcSources = new ArrayList<>();
if (StringUtils.isNotBlank(profileSchemaBody)) {
LOG.debug("Generating profile schema");
Schema profileSchema = new Schema.Parser().parse(profileSchemaBody);
String profileClassName = PROFILE_NAMESPACE + profileSchema.getName();
String profileCommonHeader = readResource(PROFILE_TEMPLATE_DIR + PROFILE_COMMON
+ _H + TEMPLATE_SUFFIX)
.replaceAll(PROFILE_CLASS_VAR, profileClassName);
objcSources.add(CommonSdkUtil.tarEntryForSources(profileCommonHeader,
KAA_ROOT + PROFILE_DIR + PROFILE_COMMON + _H));
String profileCommonSource = readResource(PROFILE_TEMPLATE_DIR + PROFILE_COMMON
+ _M + TEMPLATE_SUFFIX)
.replaceAll(PROFILE_CLASS_VAR, profileClassName);
objcSources.add(CommonSdkUtil.tarEntryForSources(profileCommonSource,
KAA_ROOT + PROFILE_DIR + PROFILE_COMMON + _M));
objcSources.addAll(generateSourcesFromSchema(profileSchema, PROFILE_GEN, PROFILE_NAMESPACE));
}
String logClassName = "";
if (StringUtils.isNotBlank(logSchemaBody)) {
LOG.debug("Generating log schema");
Schema logSchema = new Schema.Parser().parse(logSchemaBody);
logClassName = LOGGING_NAMESPACE + logSchema.getName();
String logRecordHeader = readResource(LOG_TEMPLATE_DIR + LOG_RECORD + _H + TEMPLATE_SUFFIX)
.replaceAll(LOG_RECORD_CLASS_VAR, logClassName);
objcSources.add(CommonSdkUtil.tarEntryForSources(logRecordHeader,
KAA_ROOT + LOG_DIR + LOG_RECORD + _H));
String logRecordSource = readResource(LOG_TEMPLATE_DIR + LOG_RECORD + _M + TEMPLATE_SUFFIX)
.replaceAll(LOG_RECORD_CLASS_VAR, logClassName);
objcSources.add(CommonSdkUtil.tarEntryForSources(logRecordSource,
KAA_ROOT + LOG_DIR + LOG_RECORD + _M));
String logCollector = readResource(LOG_TEMPLATE_DIR
+ LOG_COLLECTOR_INTERFACE + TEMPLATE_SUFFIX)
.replaceAll(LOG_RECORD_CLASS_VAR, logClassName);
objcSources.add(CommonSdkUtil.tarEntryForSources(
logCollector, KAA_ROOT + LOG_DIR + LOG_COLLECTOR_INTERFACE
));
String logCollectorImplHeader = readResource(
LOG_TEMPLATE_DIR + LOG_COLLECTOR_SOURCE + _H + TEMPLATE_SUFFIX)
.replaceAll(LOG_RECORD_CLASS_VAR, logClassName);
objcSources.add(CommonSdkUtil.tarEntryForSources(logCollectorImplHeader,
KAA_ROOT + LOG_DIR + LOG_COLLECTOR_SOURCE + _H));
String logCollectorImplSource = readResource(
LOG_TEMPLATE_DIR + LOG_COLLECTOR_SOURCE + _M + TEMPLATE_SUFFIX)
.replaceAll(LOG_RECORD_CLASS_VAR, logClassName);
objcSources.add(CommonSdkUtil.tarEntryForSources(logCollectorImplSource,
KAA_ROOT + LOG_DIR + LOG_COLLECTOR_SOURCE + _M));
objcSources.addAll(generateSourcesFromSchema(logSchema, LOG_GEN, LOGGING_NAMESPACE));
}
String configurationClassName = "";
if (StringUtils.isNotBlank(configurationBaseSchemaBody)) {
LOG.debug("Generating configuration schema");
Schema configurationSchema = new Schema.Parser().parse(configurationBaseSchemaBody);
configurationClassName = CONFIGURATION_NAMESPACE + configurationSchema.getName();
String configurationCommon = readResource(
CONFIGURATION_TEMPLATE_DIR + CONFIGURATION_COMMON + TEMPLATE_SUFFIX)
.replaceAll(CONFIGURATION_CLASS_VAR, configurationClassName);
objcSources.add(CommonSdkUtil.tarEntryForSources(
configurationCommon, KAA_ROOT + CONFIGURATION_DIR + CONFIGURATION_COMMON
));
String cfManagerImplHeader = readResource(
CONFIGURATION_TEMPLATE_DIR + CONFIGURATION_MANAGER_IMPL + _H + TEMPLATE_SUFFIX)
.replaceAll(CONFIGURATION_CLASS_VAR, configurationClassName);
objcSources.add(CommonSdkUtil.tarEntryForSources(cfManagerImplHeader,
KAA_ROOT + CONFIGURATION_DIR + CONFIGURATION_MANAGER_IMPL + _H));
String cfManagerImplSource = readResource(
CONFIGURATION_TEMPLATE_DIR + CONFIGURATION_MANAGER_IMPL + _M + TEMPLATE_SUFFIX)
.replaceAll(CONFIGURATION_CLASS_VAR, configurationClassName);
objcSources.add(CommonSdkUtil.tarEntryForSources(cfManagerImplSource,
KAA_ROOT + CONFIGURATION_DIR + CONFIGURATION_MANAGER_IMPL + _M));
String cfDeserializerHeader = readResource(
CONFIGURATION_TEMPLATE_DIR + CONFIGURATION_DESERIALIZER + _H + TEMPLATE_SUFFIX)
.replaceAll(CONFIGURATION_CLASS_VAR, configurationClassName);
objcSources.add(CommonSdkUtil.tarEntryForSources(cfDeserializerHeader,
KAA_ROOT + CONFIGURATION_DIR + CONFIGURATION_DESERIALIZER + _H));
String cfDeserializerSource = readResource(
CONFIGURATION_TEMPLATE_DIR + CONFIGURATION_DESERIALIZER + _M + TEMPLATE_SUFFIX)
.replaceAll(CONFIGURATION_CLASS_VAR, configurationClassName);
objcSources.add(CommonSdkUtil.tarEntryForSources(cfDeserializerSource,
KAA_ROOT + CONFIGURATION_DIR + CONFIGURATION_DESERIALIZER + _M));
objcSources.addAll(
generateSourcesFromSchema(configurationSchema, CONFIGURATION_GEN, CONFIGURATION_NAMESPACE)
);
}
if (StringUtils.isNotBlank(notificationSchemaBody)) {
LOG.debug("Generating notification schema");
Schema notificationSchema = new Schema.Parser().parse(notificationSchemaBody);
String notificationClassName = NOTIFICATION_NAMESPACE + notificationSchema.getName();
String nfCommonHeader = readResource(
NOTIFICATION_TEMPLATE_DIR + NOTIFICATION_COMMON + _H + TEMPLATE_SUFFIX)
.replaceAll(NOTIFICATION_CLASS_VAR, notificationClassName);
objcSources.add(CommonSdkUtil.tarEntryForSources(nfCommonHeader,
KAA_ROOT + NOTIFICATION_DIR + NOTIFICATION_COMMON + _H));
String nfCommonSource = readResource(
NOTIFICATION_TEMPLATE_DIR + NOTIFICATION_COMMON + _M + TEMPLATE_SUFFIX)
.replaceAll(NOTIFICATION_CLASS_VAR, notificationClassName);
objcSources.add(CommonSdkUtil.tarEntryForSources(nfCommonSource,
KAA_ROOT + NOTIFICATION_DIR + NOTIFICATION_COMMON + _M));
objcSources.addAll(
generateSourcesFromSchema(notificationSchema, NOTIFICATION_GEN, NOTIFICATION_NAMESPACE)
);
}
if (eventFamilies != null && !eventFamilies.isEmpty()) {
objcSources.addAll(ObjCEventClassesGenerator.generateEventSources(eventFamilies));
}
String kaaClient = readResource(SDK_TEMPLATE_DIR + KAA_CLIENT + TEMPLATE_SUFFIX)
.replaceAll(LOG_RECORD_CLASS_VAR, logClassName)
.replaceAll(CONFIGURATION_CLASS_VAR, configurationClassName);
objcSources.add(CommonSdkUtil.tarEntryForSources(kaaClient, KAA_ROOT + KAA_CLIENT));
String baseKaaClientHeader = readResource(
SDK_TEMPLATE_DIR + BASE_KAA_CLIENT + _H + TEMPLATE_SUFFIX)
.replaceAll(LOG_RECORD_CLASS_VAR, logClassName)
.replaceAll(CONFIGURATION_CLASS_VAR, configurationClassName);
objcSources.add(
CommonSdkUtil.tarEntryForSources(baseKaaClientHeader, KAA_ROOT + BASE_KAA_CLIENT + _H)
);
String baseKaaClientSource = readResource(
SDK_TEMPLATE_DIR + BASE_KAA_CLIENT + _M + TEMPLATE_SUFFIX)
.replaceAll(LOG_RECORD_CLASS_VAR, logClassName)
.replaceAll(CONFIGURATION_CLASS_VAR, configurationClassName);
objcSources.add(
CommonSdkUtil.tarEntryForSources(baseKaaClientSource, KAA_ROOT + BASE_KAA_CLIENT + _M)
);
String tokenVerifier = defaultVerifierToken == null ? "nil" :
"@\"" + defaultVerifierToken + "\"";
String tokenVerifierSource = readResource(
EVENT_TEMPLATE_DIR + USER_VERIFIER_CONSTANTS + TEMPLATE_SUFFIX)
.replaceAll(DEFAULT_USER_VERIFIER_TOKEN_VAR, tokenVerifier);
objcSources.add(CommonSdkUtil.tarEntryForSources(tokenVerifierSource, KAA_ROOT + EVENT_DIR
+ USER_VERIFIER_CONSTANTS));
String kaaDefaultsTemplate = readResource(SDK_TEMPLATE_DIR + KAA_DEFAULTS + TEMPLATE_SUFFIX);
objcSources.add(generateKaaDefaults(kaaDefaultsTemplate, bootstrapNodes, sdkToken,
configurationProtocolSchemaBody, defaultConfigurationData, sdkProperties));
for (TarEntryData entryData : objcSources) {
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 = SDK_PREFIX + sdkProperties.getToken() + ".tar.gz";
byte[] sdkData = sdkOutput.toByteArray();
FileData sdk = new FileData();
sdk.setFileName(sdkFileName);
sdk.setFileData(sdkData);
return sdk;
}
private TarEntryData generateKaaDefaults(String template,
List<BootstrapNodeInfo> bootstrapNodes,
String sdkToken,
String configurationProtocolSchemaBody,
byte[] defaultConfigurationData,
SdkProfileDto profileDto) {
LOG.debug("Generating kaa defaults");
final int propertiesCount = 7;
List<String> properties = new ArrayList<>(propertiesCount);
properties.add(Version.PROJECT_VERSION);
properties.add(Version.COMMIT_HASH);
properties.add(sdkToken);
properties.add(profileDto.getApplicationId());
properties.add(Base64.encodeBase64String(defaultConfigurationData));
properties.add(Base64.encodeBase64String(configurationProtocolSchemaBody.getBytes()));
properties.add(CommonSdkUtil.bootstrapNodesToString(bootstrapNodes));
String source = String.format(template, properties.toArray(new Object[propertiesCount]));
return CommonSdkUtil.tarEntryForSources(source, KAA_ROOT + KAA_DEFAULTS);
}
private List<TarEntryData> generateSourcesFromSchema(Schema schema, String sourceName,
String namespace) {
LOG.debug("Generating source with name: " + sourceName);
List<TarEntryData> tarEntries = new LinkedList<>();
try (
OutputStream headerStream = new ByteArrayOutputStream();
OutputStream sourceStream = new ByteArrayOutputStream()
) {
Compiler compiler = new ObjectiveCCompiler(schema, sourceName, headerStream, sourceStream);
compiler.setNamespacePrefix(namespace);
compiler.generate();
tarEntries.add(CommonSdkUtil.tarEntryForSources(headerStream.toString(),
KAA_GEN_FOLDER + sourceName + ".h"));
tarEntries.add(CommonSdkUtil.tarEntryForSources(sourceStream.toString(),
KAA_GEN_FOLDER + sourceName + ".m"));
} catch (Exception ex) {
LOG.error("Failed to generate ObjectiveC sdk sources", ex);
}
return tarEntries;
}
}