org.kaaproject.kaa.server.control.service.sdk.JavaSdkGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.kaaproject.kaa.server.control.service.sdk.JavaSdkGenerator.java

Source

/*
 * 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 static org.kaaproject.kaa.common.dto.admin.SdkPlatform.JAVA;

import org.apache.avro.Schema;
import org.apache.avro.compiler.specific.SpecificCompiler;
import org.apache.avro.compiler.specific.SpecificCompiler.FieldVisibility;
import org.apache.avro.generic.GenericData.StringType;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.kaaproject.kaa.common.dto.admin.SdkPlatform;
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.gen.BootstrapNodeInfo;
import org.kaaproject.kaa.server.control.service.sdk.compiler.JavaDynamicBean;
import org.kaaproject.kaa.server.control.service.sdk.compiler.JavaDynamicCompiler;
import org.kaaproject.kaa.server.control.service.sdk.compress.ZipEntryData;
import org.kaaproject.kaa.server.control.service.sdk.event.EventFamilyMetadata;
import org.kaaproject.kaa.server.control.service.sdk.event.JavaEventClassesGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import javax.tools.JavaFileObject.Kind;

public class JavaSdkGenerator extends SdkGenerator {

    private static final Logger LOG = LoggerFactory.getLogger(JavaSdkGenerator.class);

    private static final String JAVA_SDK_DIR = "sdk/java";

    private static final String JAVA_SDK_PREFIX = "kaa-java-ep-sdk-";

    private static final String ANDROID_SDK_DIR = "sdk/android";

    private static final String ANDROID_SDK_PREFIX = "kaa-android-ep-sdk-";

    private static final String CLIENT_PROPERTIES = "client.properties";

    private static final String BUILD_VERSION = "build.version";

    private static final String BUILD_COMMIT_HASH = "build.commit_hash";

    private static final String BOOTSTRAP_SERVERS_PROPERTY = "transport.bootstrap.servers";

    private static final String SDK_TOKEN_PROPERTY = "sdk_token";

    private static final String CONFIG_DATA_DEFAULT_PROPERTY = "config.data.default";

    private static final String CONFIG_SCHEMA_DEFAULT_PROPERTY = "config.schema.default";

    private static final String KAA_CLIENT_SOURCE_TEMPLATE = "sdk/java/KaaClient.java.template";

    private static final String BASE_KAA_CLIENT_SOURCE_TEMPLATE = "sdk/java/BaseKaaClient.java.template";

    private static final String CONFIGURATION_MANAGER_SOURCE_TEMPLATE = "sdk/java/cf/ConfigurationManager.java.template";

    private static final String CONFIGURATION_MANAGER_IMPL_SOURCE_TEMPLATE = "sdk/java/cf/ResyncConfigurationManager.java.template";

    private static final String CONFIGURATION_LISTENER_SOURCE_TEMPLATE = "sdk/java/cf/ConfigurationListener.java.template";

    private static final String CONFIGURATION_DESERIALIZER_SOURCE_TEMPLATE = "sdk/java/cf/ConfigurationDeserializer.java.template";

    private static final String NOTIFICATION_LISTENER_SOURCE_TEMPLATE = "sdk/java/nf/NotificationListener.java.template";

    private static final String NOTIFICATION_DESERIALIZER_SOURCE_TEMPLATE = "sdk/java/nf/NotificationDeserializer.java.template";

    private static final String PROFILE_CONTAINER_SOURCE_TEMPLATE = "sdk/java/profile/ProfileContainer.java.template";

    private static final String DEFAULT_PROFILE_CONTAINER_SOURCE_TEMPLATE = "sdk/java/profile/DefaultProfileContainer.java.template";

    private static final String PROFILE_SERIALIZER_SOURCE_TEMPLATE = "sdk/java/profile/ProfileSerializer.java.template";

    private static final String DEFAULT_PROFILE_SERIALIZER_SOURCE_TEMPLATE = "sdk/java/profile/DefaultProfileSerializer.java.template";

    private static final String LOG_RECORD_SOURCE_TEMPLATE = "sdk/java/log/LogRecord.java.template";

    private static final String LOG_COLLECTOR_INTERFACE_TEMPLATE = "sdk/java/log/LogCollector.java.template";

    private static final String LOG_COLLECTOR_SOURCE_TEMPLATE = "sdk/java/log/DefaultLogCollector.java.template";

    private static final String USER_VERIFIER_CONSTANTS_SOURCE_TEMPLATE = "sdk/java/event/UserVerifierConstants.java.template";

    private static final String PROFILE_CONTAINER = "ProfileContainer";

    private static final String DEFAULT_PROFILE_CONTAINER = "DefaultProfileContainer";

    private static final String PROFILE_SERIALIZER = "ProfileSerializer";

    private static final String CONFIGURATION_MANAGER = "ConfigurationManager";

    private static final String CONFIGURATION_MANAGER_IMPL = "ResyncConfigurationManager";

    private static final String CONFIGURATION_LISTENER = "ConfigurationListener";

    private static final String CONFIGURATION_DESERIALIZER = "ConfigurationDeserializer";

    private static final String NOTIFICATION_LISTENER = "NotificationListener";

    private static final String NOTIFICATION_DESERIALIZER = "NotificationDeserializer";

    private static final String USER_VERIFIER_CONSTANTS = "UserVerifierConstants";

    private static final int DEFAULT_SCHEMA_VERSION = 1;

    private static final int DEFAULT_PROFILE_SCHEMA_VERSION = 0;

    private static final String KAA_CLIENT = "KaaClient";

    private static final String BASE_KAA_CLIENT = "BaseKaaClient";

    private static final String LOG_RECORD = "LogRecord";

    private static final String LOG_COLLECTOR_INTERFACE = "LogCollector";

    private static final String LOG_COLLECTOR_SOURCE = "DefaultLogCollector";

    private static final String PROFILE_CLASS_PACKAGE_VAR = "\\$\\{profile_class_package\\}";

    private static final String PROFILE_CLASS_VAR = "\\$\\{profile_class\\}";

    private static final String CONFIGURATION_CLASS_PACKAGE_VAR = "\\$\\{configuration_class_package\\}";

    private static final String CONFIGURATION_CLASS_VAR = "\\$\\{configuration_class\\}";

    private static final String NOTIFICATION_CLASS_PACKAGE_VAR = "\\$\\{notification_class_package\\}";

    private static final String NOTIFICATION_CLASS_VAR = "\\$\\{notification_class\\}";

    private static final String LOG_RECORD_CLASS_PACKAGE_VAR = "\\$\\{log_record_class_package\\}";

    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 JAVA_SOURCE_COMPILER_RELEASE = "7";

    private static final String JAVA_TARGET_COMPILER_RELEASE = "7";

    private static final SecureRandom RANDOM = new SecureRandom();

    private final SdkPlatform sdkPlatform;

    public JavaSdkGenerator(SdkPlatform sdkPlatform) {
        this.sdkPlatform = sdkPlatform;
    }

    /**
     * Generate schema class.
     *
     * @param schema        the schema
     * @param uniqueSchemas the unique schemas
     * @return the list
     * @throws IOException Signals that an I/O exception has occurred.
     */
    public static List<JavaDynamicBean> generateSchemaSources(Schema schema, Map<String, Schema> uniqueSchemas)
            throws IOException {
        SpecificCompiler compiler = new SpecificCompiler(schema);
        compiler.setStringType(StringType.String);
        compiler.setFieldVisibility(FieldVisibility.PRIVATE);

        File tmpdir = new File(System.getProperty("java.io.tmpdir"));
        long value = RANDOM.nextLong();
        if (value == Long.MIN_VALUE) {
            // corner case
            value = 0;
        } else {
            value = Math.abs(value);
        }
        File tmpOutputDir = new File(tmpdir, "tmp-gen-" + Long.toString(value));
        tmpOutputDir.mkdirs();

        compiler.compileToDestination(null, tmpOutputDir);

        List<JavaDynamicBean> sources = getJavaSources(tmpOutputDir, uniqueSchemas);

        tmpOutputDir.delete();

        return sources;
    }

    /**
     * Gets the java sources.
     *
     * @param srcDir the src dir
     * @return the java sources
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private static List<JavaDynamicBean> getJavaSources(File srcDir, Map<String, Schema> uniqueSchemas)
            throws IOException {
        List<JavaDynamicBean> result = new ArrayList<JavaDynamicBean>();
        File[] files = srcDir.listFiles();
        for (File f : files) {
            if (f.isDirectory()) {
                result.addAll(getJavaSources(f, uniqueSchemas));
            } else if (f.getName().endsWith(Kind.SOURCE.extension)) {
                int index = f.getName().indexOf('.');
                String className = f.getName().substring(0, index);
                String sourceCode = readFile(f);

                String classPackageAndName = "";

                String[] sourceLines = sourceCode.split("\n");
                for (String ss : sourceLines) {
                    if (ss.startsWith("package ")) {
                        classPackageAndName = ss.replace("package ", "").trim().replaceAll(";", ".") + className;
                        break;
                    }
                }

                if (uniqueSchemas.containsKey(classPackageAndName)) {
                    uniqueSchemas.remove(classPackageAndName);
                    JavaDynamicBean sourceObject = new JavaDynamicBean(className, sourceCode);
                    result.add(sourceObject);
                }
            }
        }
        return result;
    }

    /*
     * (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 configurationSchemaBody, byte[] defaultConfigurationData,
            List<EventFamilyMetadata> eventFamilies, String logSchemaBody) throws Exception {

        final String sdkToken = sdkProfile.getToken();
        final Integer profileSchemaVersion = sdkProfile.getProfileSchemaVersion();
        final Integer notificationSchemaVersion = sdkProfile.getNotificationSchemaVersion();
        final Integer logSchemaVersion = sdkProfile.getLogSchemaVersion();

        final Schema configurationSchema = new Schema.Parser().parse(configurationSchemaBody);
        final Schema profileSchema = new Schema.Parser().parse(profileSchemaBody);
        final Schema notificationSchema = new Schema.Parser().parse(notificationSchemaBody);
        final Schema logSchema = new Schema.Parser().parse(logSchemaBody);

        List<String> flatEventClassCtlSchemas = new ArrayList<>();
        eventFamilies.forEach(ecf -> flatEventClassCtlSchemas.addAll(ecf.getRawCtlsSchemas()));
        List<Schema> eventClassCtlSchemas = new LinkedList<>();
        if (!flatEventClassCtlSchemas.isEmpty()) {
            for (String flatCtlSchema : flatEventClassCtlSchemas) {
                Schema eventClassCtlSchema = new Schema.Parser().parse(flatCtlSchema);
                eventClassCtlSchemas.add(eventClassCtlSchema);
            }
        }

        List<Schema> schemasToCheck = new LinkedList<>();
        schemasToCheck.add(configurationSchema);
        schemasToCheck.add(profileSchema);
        schemasToCheck.add(notificationSchema);
        schemasToCheck.add(logSchema);
        schemasToCheck.addAll(eventClassCtlSchemas);

        Map<String, Schema> uniqueSchemasMap = SchemaUtil.getUniqueSchemasMap(schemasToCheck);

        String sdkTemplateLocation;
        if (sdkPlatform == JAVA) {
            sdkTemplateLocation = Environment.getServerHomeDir() + "/" + JAVA_SDK_DIR + "/" + JAVA_SDK_PREFIX
                    + buildVersion + ".jar";
            LOG.debug("Lookup Java SDK template: {}", sdkTemplateLocation);
        } else { // ANDROID
            sdkTemplateLocation = Environment.getServerHomeDir() + "/" + ANDROID_SDK_DIR + "/" + ANDROID_SDK_PREFIX
                    + buildVersion + ".jar";
            LOG.debug("Lookup Android SDK template: {}", sdkTemplateLocation);
        }

        File sdkTemplateFile = new File(sdkTemplateLocation);
        ZipFile templateArhive = new ZipFile(sdkTemplateFile);

        Map<String, ZipEntryData> replacementData = new HashMap<String, ZipEntryData>();

        ZipEntry clientPropertiesEntry = templateArhive.getEntry(CLIENT_PROPERTIES);
        byte[] clientPropertiesData = generateClientProperties(templateArhive.getInputStream(clientPropertiesEntry),
                bootstrapNodes, sdkToken, configurationProtocolSchemaBody, defaultConfigurationData);

        replacementData.put(CLIENT_PROPERTIES,
                new ZipEntryData(new ZipEntry(CLIENT_PROPERTIES), clientPropertiesData));

        List<JavaDynamicBean> javaSources = new ArrayList<JavaDynamicBean>();

        String configurationClassName = configurationSchema.getName();
        String configurationClassPackage = configurationSchema.getNamespace();

        javaSources.addAll(generateSchemaSources(configurationSchema, uniqueSchemasMap));

        String configurationManagerImplTemplate = readResource(CONFIGURATION_MANAGER_IMPL_SOURCE_TEMPLATE);
        String configurationManagerImplSource = configurationManagerImplTemplate
                .replaceAll(CONFIGURATION_CLASS_PACKAGE_VAR, configurationClassPackage)
                .replaceAll(CONFIGURATION_CLASS_VAR, configurationClassName);

        JavaDynamicBean configurationManagerImplClassBean = new JavaDynamicBean(CONFIGURATION_MANAGER_IMPL,
                configurationManagerImplSource);
        javaSources.add(configurationManagerImplClassBean);

        String configurationManagerTemplate = readResource(CONFIGURATION_MANAGER_SOURCE_TEMPLATE);
        String configurationManagerSource = configurationManagerTemplate
                .replaceAll(CONFIGURATION_CLASS_PACKAGE_VAR, configurationClassPackage)
                .replaceAll(CONFIGURATION_CLASS_VAR, configurationClassName);

        JavaDynamicBean configurationManagerClassBean = new JavaDynamicBean(CONFIGURATION_MANAGER,
                configurationManagerSource);
        javaSources.add(configurationManagerClassBean);

        String configurationListenerTemplate = readResource(CONFIGURATION_LISTENER_SOURCE_TEMPLATE);
        String configurationListenerSource = configurationListenerTemplate
                .replaceAll(CONFIGURATION_CLASS_PACKAGE_VAR, configurationClassPackage)
                .replaceAll(CONFIGURATION_CLASS_VAR, configurationClassName);

        JavaDynamicBean configurationListenerClassBean = new JavaDynamicBean(CONFIGURATION_LISTENER,
                configurationListenerSource);
        javaSources.add(configurationListenerClassBean);

        String configurationDeserializerSourceTemplate = readResource(CONFIGURATION_DESERIALIZER_SOURCE_TEMPLATE);
        String configurationDeserializerSource = configurationDeserializerSourceTemplate
                .replaceAll(CONFIGURATION_CLASS_PACKAGE_VAR, configurationClassPackage)
                .replaceAll(CONFIGURATION_CLASS_VAR, configurationClassName);

        JavaDynamicBean configurationDeserializerClassBean = new JavaDynamicBean(CONFIGURATION_DESERIALIZER,
                configurationDeserializerSource);
        javaSources.add(configurationDeserializerClassBean);

        String profileClassName = profileSchema.getName();
        String profileClassPackage = profileSchema.getNamespace();

        if (profileSchemaVersion != DEFAULT_PROFILE_SCHEMA_VERSION) {
            javaSources.addAll(generateSchemaSources(profileSchema, uniqueSchemasMap));
        }

        String profileContainerTemplate = readResource(PROFILE_CONTAINER_SOURCE_TEMPLATE);
        String profileContainerSource = profileContainerTemplate
                .replaceAll(PROFILE_CLASS_PACKAGE_VAR, profileClassPackage)
                .replaceAll(PROFILE_CLASS_VAR, profileClassName);

        JavaDynamicBean profileContainerClassBean = new JavaDynamicBean(PROFILE_CONTAINER, profileContainerSource);

        javaSources.add(profileContainerClassBean);

        String defaultProfileContainerTemplate = readResource(DEFAULT_PROFILE_CONTAINER_SOURCE_TEMPLATE);
        String defaultProfileContainerSource = defaultProfileContainerTemplate
                .replaceAll(PROFILE_CLASS_PACKAGE_VAR, profileClassPackage)
                .replaceAll(PROFILE_CLASS_VAR, profileClassName);
        JavaDynamicBean defaultProfileContainerClassBean = new JavaDynamicBean(DEFAULT_PROFILE_CONTAINER,
                defaultProfileContainerSource);
        javaSources.add(defaultProfileContainerClassBean);

        String profileSerializerTemplate;
        if (profileSchemaVersion == DEFAULT_PROFILE_SCHEMA_VERSION) {
            profileSerializerTemplate = readResource(DEFAULT_PROFILE_SERIALIZER_SOURCE_TEMPLATE);
        } else {
            profileSerializerTemplate = readResource(PROFILE_SERIALIZER_SOURCE_TEMPLATE);
        }
        String profileSerializerSource = profileSerializerTemplate
                .replaceAll(PROFILE_CLASS_PACKAGE_VAR, profileClassPackage)
                .replaceAll(PROFILE_CLASS_VAR, profileClassName);

        JavaDynamicBean profileSerializerClassBean = new JavaDynamicBean(PROFILE_SERIALIZER,
                profileSerializerSource);

        javaSources.add(profileSerializerClassBean);

        String notificationClassName = notificationSchema.getName();
        String notificationClassPackage = notificationSchema.getNamespace();

        if (notificationSchemaVersion != DEFAULT_SCHEMA_VERSION) {
            javaSources.addAll(generateSchemaSources(notificationSchema, uniqueSchemasMap));
        }

        String notificationListenerTemplate = readResource(NOTIFICATION_LISTENER_SOURCE_TEMPLATE);
        String notificationListenerSource = notificationListenerTemplate
                .replaceAll(NOTIFICATION_CLASS_PACKAGE_VAR, notificationClassPackage)
                .replaceAll(NOTIFICATION_CLASS_VAR, notificationClassName);

        JavaDynamicBean notificationListenerClassBean = new JavaDynamicBean(NOTIFICATION_LISTENER,
                notificationListenerSource);
        javaSources.add(notificationListenerClassBean);

        String notificationDeserializerSourceTemplate = readResource(NOTIFICATION_DESERIALIZER_SOURCE_TEMPLATE);

        String notificationDeserializerSource = notificationDeserializerSourceTemplate
                .replaceAll(NOTIFICATION_CLASS_PACKAGE_VAR, notificationClassPackage)
                .replaceAll(NOTIFICATION_CLASS_VAR, notificationClassName);

        JavaDynamicBean notificationDeserializerClassBean = new JavaDynamicBean(NOTIFICATION_DESERIALIZER,
                notificationDeserializerSource);
        javaSources.add(notificationDeserializerClassBean);

        if (logSchemaVersion != DEFAULT_SCHEMA_VERSION) {
            javaSources.addAll(generateSchemaSources(logSchema, uniqueSchemasMap));
        }

        String logRecordTemplate = readResource(LOG_RECORD_SOURCE_TEMPLATE);
        String logRecordSource = logRecordTemplate
                .replaceAll(LOG_RECORD_CLASS_PACKAGE_VAR, logSchema.getNamespace())
                .replaceAll(LOG_RECORD_CLASS_VAR, logSchema.getName());

        String logCollectorInterfaceTemplate = readResource(LOG_COLLECTOR_INTERFACE_TEMPLATE);
        String logCollectorInterface = logCollectorInterfaceTemplate
                .replaceAll(LOG_RECORD_CLASS_PACKAGE_VAR, logSchema.getNamespace())
                .replaceAll(LOG_RECORD_CLASS_VAR, logSchema.getName());

        String logCollectorSourceTemplate = readResource(LOG_COLLECTOR_SOURCE_TEMPLATE);
        String logCollectorSource = logCollectorSourceTemplate
                .replaceAll(LOG_RECORD_CLASS_PACKAGE_VAR, logSchema.getNamespace())
                .replaceAll(LOG_RECORD_CLASS_VAR, logSchema.getName());

        JavaDynamicBean logRecordClassBean = new JavaDynamicBean(LOG_RECORD, logRecordSource);
        JavaDynamicBean logCollectorInterfaceClassBean = new JavaDynamicBean(LOG_COLLECTOR_INTERFACE,
                logCollectorInterface);
        JavaDynamicBean logCollectorSourceClassBean = new JavaDynamicBean(LOG_COLLECTOR_SOURCE, logCollectorSource);

        javaSources.add(logRecordClassBean);
        javaSources.add(logCollectorInterfaceClassBean);
        javaSources.add(logCollectorSourceClassBean);

        if (eventFamilies != null && !eventFamilies.isEmpty()) {
            for (Schema ctlSchema : eventClassCtlSchemas) {
                javaSources.addAll(generateSchemaSources(ctlSchema, uniqueSchemasMap));
            }
            javaSources.addAll(JavaEventClassesGenerator.generateEventClasses(eventFamilies));
        }

        String defaultVerifierToken = sdkProfile.getDefaultVerifierToken();
        String userVerifierConstantsTemplate = readResource(USER_VERIFIER_CONSTANTS_SOURCE_TEMPLATE);
        if (defaultVerifierToken == null) {
            defaultVerifierToken = "null";
        } else {
            defaultVerifierToken = "\"" + defaultVerifierToken + "\"";
        }
        String userVerifierConstantsSource = userVerifierConstantsTemplate
                .replaceAll(DEFAULT_USER_VERIFIER_TOKEN_VAR, defaultVerifierToken);

        JavaDynamicBean userVerifierConstantsClassBean = new JavaDynamicBean(USER_VERIFIER_CONSTANTS,
                userVerifierConstantsSource);
        javaSources.add(userVerifierConstantsClassBean);

        String kaaClientTemplate = readResource(KAA_CLIENT_SOURCE_TEMPLATE);

        String kaaClientSource = kaaClientTemplate
                .replaceAll(LOG_RECORD_CLASS_PACKAGE_VAR, logSchema.getNamespace())
                .replaceAll(LOG_RECORD_CLASS_VAR, logSchema.getName())
                .replaceAll(CONFIGURATION_CLASS_PACKAGE_VAR, configurationClassPackage)
                .replaceAll(CONFIGURATION_CLASS_VAR, configurationClassName);

        JavaDynamicBean kaaClientClassBean = new JavaDynamicBean(KAA_CLIENT, kaaClientSource);
        javaSources.add(kaaClientClassBean);

        String baseKaaClientTemplate = readResource(BASE_KAA_CLIENT_SOURCE_TEMPLATE);
        String baseKaaClientSource = baseKaaClientTemplate
                .replaceAll(LOG_RECORD_CLASS_PACKAGE_VAR, logSchema.getNamespace())
                .replaceAll(LOG_RECORD_CLASS_VAR, logSchema.getName())
                .replaceAll(CONFIGURATION_CLASS_PACKAGE_VAR, configurationClassPackage)
                .replaceAll(CONFIGURATION_CLASS_VAR, configurationClassName);

        JavaDynamicBean baseKaaClientClassBean = new JavaDynamicBean(BASE_KAA_CLIENT, baseKaaClientSource);
        javaSources.add(baseKaaClientClassBean);

        packageSources(javaSources, replacementData);

        ByteArrayOutputStream sdkOutput = new ByteArrayOutputStream();
        ZipOutputStream sdkFile = new ZipOutputStream(sdkOutput);

        Enumeration<? extends ZipEntry> entries = templateArhive.entries();

        while (entries.hasMoreElements()) {
            ZipEntry entry = entries.nextElement();
            if (replacementData.containsKey(entry.getName())) {
                ZipEntryData replacementEntry = replacementData.remove(entry.getName());
                sdkFile.putNextEntry(replacementEntry.getEntry());
                sdkFile.write(replacementEntry.getData());
            } else {
                sdkFile.putNextEntry(entry);
                if (!entry.isDirectory()) {
                    IOUtils.copy(templateArhive.getInputStream(entry), sdkFile);
                }
            }
            sdkFile.closeEntry();
        }
        templateArhive.close();

        for (String entryName : replacementData.keySet()) {
            ZipEntryData replacementEntry = replacementData.get(entryName);
            sdkFile.putNextEntry(replacementEntry.getEntry());
            sdkFile.write(replacementEntry.getData());
            sdkFile.closeEntry();
        }

        sdkFile.close();

        String fileNamePrefix = (sdkPlatform == JAVA ? JAVA_SDK_PREFIX : ANDROID_SDK_PREFIX);
        String sdkFileName = fileNamePrefix + sdkProfile.getToken() + ".jar";

        byte[] sdkData = sdkOutput.toByteArray();

        FileData sdk = new FileData();
        sdk.setFileName(sdkFileName);
        sdk.setFileData(sdkData);
        return sdk;
    }

    /**
     * Package sources.
     *
     * @param javaSources the java sources
     * @param data        the data
     */
    private void packageSources(List<JavaDynamicBean> javaSources, Map<String, ZipEntryData> data) {
        JavaDynamicCompiler dynamicCompiler = new JavaDynamicCompiler();
        dynamicCompiler.init();
        for (JavaDynamicBean bean : javaSources) {
            LOG.debug("Compiling bean [{}]...", bean.getName());
            LOG.trace("Bean source:\n{}", bean.getCharContent(true));
            Stream<String> sourceLines = Arrays.stream(bean.getCharContent(true).split("\n"));

            String packageLine = sourceLines.filter(line -> line.startsWith("package")).findFirst().orElse("");

            String sourceFileName = packageLine.replaceAll("package", "").replaceAll("\\.|;", "/").trim()
                    + bean.getName();

            data.put(sourceFileName,
                    new ZipEntryData(new ZipEntry(sourceFileName), bean.getCharContent(true).getBytes()));
        }
        Collection<JavaDynamicBean> compiledObjects = dynamicCompiler.compile(javaSources, "-source",
                JAVA_SOURCE_COMPILER_RELEASE, "-target", JAVA_TARGET_COMPILER_RELEASE);
        for (JavaDynamicBean compiledObject : compiledObjects) {
            String className = compiledObject.getName();
            String classFileName = className.replace('.', '/') + Kind.CLASS.extension;
            ZipEntry classFile = new ZipEntry(classFileName);
            ZipEntryData zipEntryData = new ZipEntryData(classFile, compiledObject.getBytes());
            data.put(classFileName, zipEntryData);
        }
    }

    /**
     * Generate client properties.
     *
     * @param clientPropertiesStream          the client properties stream
     * @param bootstrapNodes                  the bootstrap nodes
     * @param configurationProtocolSchemaBody the configuration protocol schema body
     * @param defaultConfigurationData        the default configuration data
     * @return the byte[]
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private byte[] generateClientProperties(InputStream clientPropertiesStream,
            List<BootstrapNodeInfo> bootstrapNodes, String sdkToken, String configurationProtocolSchemaBody,
            byte[] defaultConfigurationData) throws IOException {

        Properties clientProperties = new Properties();
        clientProperties.load(clientPropertiesStream);

        LOG.debug("[sdk generateClientProperties] bootstrapNodes.size(): {}", bootstrapNodes.size());

        clientProperties.put(BUILD_VERSION, Version.PROJECT_VERSION);
        clientProperties.put(BUILD_COMMIT_HASH, Version.COMMIT_HASH);
        clientProperties.put(BOOTSTRAP_SERVERS_PROPERTY, CommonSdkUtil.bootstrapNodesToString(bootstrapNodes));
        clientProperties.put(SDK_TOKEN_PROPERTY, sdkToken);
        clientProperties.put(CONFIG_SCHEMA_DEFAULT_PROPERTY, configurationProtocolSchemaBody);
        clientProperties.put(CONFIG_DATA_DEFAULT_PROPERTY, Base64.encodeBase64String(defaultConfigurationData));

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        clientProperties.store(baos, "");

        return baos.toByteArray();
    }

}