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

Java tutorial

Introduction

Here is the source code for org.kaaproject.kaa.server.control.service.sdk.CSdkGenerator.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 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.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.kaaproject.kaa.avro.avrogen.StyleUtils;
import org.kaaproject.kaa.avro.avrogen.compiler.CCompiler;
import org.kaaproject.kaa.avro.avrogen.compiler.Compiler;
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.control.service.sdk.compress.TarEntryData;
import org.kaaproject.kaa.server.control.service.sdk.event.CEventSourcesGenerator;
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.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class CSdkGenerator extends SdkGenerator {

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

    private static final String C_SDK_DIR = "sdk/c";
    private static final String TEMPLATE_DIR = "sdk/c";
    /**
     * The KAA_SRC_FOLDER variable is also set in CMakeList.txt located in the root folder of the C
     * SDK project.
     */
    private static final String KAA_SRC_FOLDER = "src/kaa";
    private static final String KAA_GEN_SOURCE_DIR = KAA_SRC_FOLDER + "/gen/";

    private static final String C_SDK_PREFIX = "kaa-c-ep-sdk-";

    private static final String KAA_SOURCE_PREFIX = "kaa";

    private static final String C_HEADER_SUFFIX = ".h";
    private static final String C_SOURCE_SUFFIX = ".c";

    private static final String KAA_CMAKEGEN = "listfiles/CMakeGen.cmake";
    private static final String KAA_DEFAULTS_HEADER = KAA_SRC_FOLDER + "/kaa_defaults.h";

    private static final String LOGGING_HEADER = KAA_GEN_SOURCE_DIR + "kaa_logging_definitions.h";
    private static final String PROFILE_HEADER = KAA_GEN_SOURCE_DIR + "kaa_profile_definitions.h";
    private static final String CONFIGURATION_HEADER = KAA_GEN_SOURCE_DIR + "kaa_configuration_definitions.h";

    private static final String NOTIFICATION_HEADER = KAA_GEN_SOURCE_DIR + "kaa_notification_definitions.h";

    private static final String PROFILE_SOURCE_NAME_PATTERN = "kaa_profile_gen";
    private static final String LOGGING_SOURCE_NAME_PATTERN = "kaa_logging_gen";
    private static final String CONFIGURATION_SOURCE_NAME_PATTERN = "kaa_configuration_gen";
    private static final String NOTIFICATION_SOURCE_NAME_PATTERN = "kaa_notification_gen";

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

    private final VelocityEngine velocityEngine;

    /**
     * Instantiates a new CSdkGenerator.
     */
    public CSdkGenerator() {
        velocityEngine = new VelocityEngine();

        velocityEngine.addProperty("resource.loader", "class, file");

        velocityEngine.addProperty("class.resource.loader.class",
                "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");

        velocityEngine.addProperty("file.resource.loader.class",
                "org.apache.velocity.runtime.resource.loader.FileResourceLoader");
        velocityEngine.addProperty("file.resource.loader.path", "/, .");
        velocityEngine.setProperty("runtime.references.strict", true);

        velocityEngine.setProperty("runtime.log.logsystem.class", "org.apache.velocity.runtime.log.NullLogSystem");
    }

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

        String sdkToken = sdkProfile.getToken();
        Integer profileSchemaVersion = sdkProfile.getProfileSchemaVersion();
        String defaultVerifierToken = sdkProfile.getDefaultVerifierToken();

        String sdkTemplateLocation = Environment.getServerHomeDir() + "/" + C_SDK_DIR + "/" + C_SDK_PREFIX
                + buildVersion + ".tar.gz";

        LOG.debug("Lookup 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> sources = new ArrayList<>();

        if (!StringUtils.isBlank(profileSchemaBody)) {
            sources.addAll(generateProfileSources(profileSchemaBody));
        }

        if (!StringUtils.isBlank(logSchemaBody)) {
            sources.addAll(generateLogSources(logSchemaBody));
        }

        if (!StringUtils.isBlank(configurationBaseSchemaBody)) {
            sources.addAll(generateConfigurationSources(configurationBaseSchemaBody));
        }

        if (!StringUtils.isBlank(notificationSchemaBody)) {
            sources.addAll(generateNotificationSources(notificationSchemaBody));
        }

        if (eventFamilies != null && !eventFamilies.isEmpty()) {
            sources.addAll(CEventSourcesGenerator.generateEventSources(eventFamilies));
        }

        for (TarEntryData entryData : sources) {
            replacementData.put(entryData.getEntry().getName(), entryData);
        }

        ArchiveEntry archiveEntry;
        while ((archiveEntry = templateArchive.getNextEntry()) != null) {
            if (!archiveEntry.isDirectory()) {
                if (archiveEntry.getName().equals(KAA_DEFAULTS_HEADER)) {
                    // TODO: eliminate schema versions and substitute them for a single sdkToken
                    byte[] kaaDefaultsData = generateKaaDefaults(bootstrapNodes, sdkToken, profileSchemaVersion,
                            configurationProtocolSchemaBody, defaultConfigurationData, defaultVerifierToken);

                    TarArchiveEntry kaaDefaultsEntry = new TarArchiveEntry(KAA_DEFAULTS_HEADER);
                    kaaDefaultsEntry.setSize(kaaDefaultsData.length);
                    sdkFile.putArchiveEntry(kaaDefaultsEntry);
                    sdkFile.write(kaaDefaultsData);
                } else if (archiveEntry.getName().equals(KAA_CMAKEGEN)) {
                    // Ignore duplicate source names
                    List<String> sourceNames = new LinkedList<>();
                    for (TarEntryData sourceEntry : sources) {
                        String fileName = sourceEntry.getEntry().getName();
                        if (fileName.endsWith(C_SOURCE_SUFFIX) && !sourceNames.contains(fileName)) {
                            sourceNames.add(fileName);
                        }
                    }

                    VelocityContext context = new VelocityContext();
                    context.put("sourceNames", sourceNames);
                    String sourceData = generateSourceFromTemplate(TEMPLATE_DIR + File.separator + "CMakeGen.vm",
                            context);

                    TarArchiveEntry kaaCMakeEntry = new TarArchiveEntry(KAA_CMAKEGEN);
                    kaaCMakeEntry.setSize(sourceData.length());
                    sdkFile.putArchiveEntry(kaaCMakeEntry);
                    sdkFile.write(sourceData.getBytes());
                } else 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 = C_SDK_PREFIX + sdkProfile.getToken() + ".tar.gz";

        FileData sdk = new FileData();
        sdk.setFileName(sdkFileName);
        sdk.setFileData(sdkOutput.toByteArray());

        return sdk;
    }

    private List<TarEntryData> generateSourcesFromSchema(Schema schema, String sourceName, String namespace) {
        List<TarEntryData> tarEntries = new LinkedList<>();

        try (OutputStream headerStream = new ByteArrayOutputStream();
                OutputStream sourceStream = new ByteArrayOutputStream()) {
            Compiler compiler = new CCompiler(schema, sourceName, headerStream, sourceStream);
            compiler.setNamespacePrefix(namespace);
            compiler.generate();

            tarEntries.add(
                    createTarEntry(KAA_GEN_SOURCE_DIR + sourceName + C_HEADER_SUFFIX, headerStream.toString()));

            tarEntries.add(
                    createTarEntry(KAA_GEN_SOURCE_DIR + sourceName + C_SOURCE_SUFFIX, sourceStream.toString()));

        } catch (Exception ex) {
            LOG.error("Failed to generate C sdk sources", ex);
        }

        return tarEntries;
    }

    private String generateSourceFromTemplate(String templateFullName, VelocityContext context) {
        StringWriter writer = new StringWriter();
        velocityEngine.getTemplate(templateFullName).merge(context, writer);
        return writer.toString();
    }

    private String processHeaderTemplate(String templateName, Schema schema, String namespace) {
        VelocityContext context = new VelocityContext();
        context.put("record_name", StyleUtils.toLowerUnderScore(schema.getName()));
        context.put("namespace", namespace);
        return generateSourceFromTemplate(TEMPLATE_DIR + File.separator + templateName, context);
    }

    private TarEntryData createTarEntry(String tarEntryName, String data) {
        TarArchiveEntry entry = new TarArchiveEntry(tarEntryName);
        entry.setSize(data.length());
        return new TarEntryData(entry, data.getBytes());
    }

    /**
     * Generate client properties.
     *
     * @param bootstrapNodes                  the bootstrap nodes
     * @param sdkToken                        the sdk token
     * @param profileSchemaVersion            the profile schema version
     * @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[] generateKaaDefaults(List<BootstrapNodeInfo> bootstrapNodes, String sdkToken,
            int profileSchemaVersion, String configurationProtocolSchemaBody, byte[] defaultConfigurationData,
            String defaultVerifierToken) throws IOException {

        VelocityContext context = new VelocityContext();

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

        context.put("build_version", Version.PROJECT_VERSION);
        context.put("build_commit_hash", Version.COMMIT_HASH);
        context.put("sdk_token", sdkToken);

        context.put("profile_version", profileSchemaVersion);
        context.put("user_verifier_token", (defaultVerifierToken != null ? defaultVerifierToken : ""));

        context.put("bootstrapNodes", bootstrapNodes);
        context.put("configurationData", defaultConfigurationData);

        context.put("Base64", Base64.class);
        context.put("Integer", Integer.class);
        context.put("ServerNameUtil", ServerNameUtil.class);

        return generateSourceFromTemplate(TEMPLATE_DIR + File.separator + "kaa_defaults.hvm", context).getBytes();
    }

    private List<TarEntryData> generateProfileSources(String profileSchemaBody) {
        Schema schema = new Schema.Parser().parse(profileSchemaBody);
        List<TarEntryData> tarEntries = new LinkedList<>();

        tarEntries.add(createTarEntry(PROFILE_HEADER,
                processHeaderTemplate("kaa_profile_definitions.hvm", schema, PROFILE_NAMESPACE)));

        tarEntries.addAll(generateSourcesFromSchema(schema, PROFILE_SOURCE_NAME_PATTERN, PROFILE_NAMESPACE));

        return tarEntries;
    }

    private List<TarEntryData> generateLogSources(String logSchemaBody) {
        Schema schema = new Schema.Parser().parse(logSchemaBody);
        List<TarEntryData> tarEntries = new LinkedList<>();

        tarEntries.add(createTarEntry(LOGGING_HEADER,
                processHeaderTemplate("kaa_logging_definitions.hvm", schema, LOGGING_NAMESPACE)));

        tarEntries.addAll(generateSourcesFromSchema(schema, LOGGING_SOURCE_NAME_PATTERN, LOGGING_NAMESPACE));

        return tarEntries;
    }

    private List<TarEntryData> generateConfigurationSources(String configurationSchemaBody) {
        Schema schema = new Schema.Parser().parse(configurationSchemaBody);
        List<TarEntryData> tarEntries = new LinkedList<>();

        tarEntries.add(createTarEntry(CONFIGURATION_HEADER,
                processHeaderTemplate("kaa_configuration_definitions.hvm", schema, CONFIGURATION_NAMESPACE)));

        tarEntries.addAll(
                generateSourcesFromSchema(schema, CONFIGURATION_SOURCE_NAME_PATTERN, CONFIGURATION_NAMESPACE));

        return tarEntries;
    }

    private List<TarEntryData> generateNotificationSources(String notificationSchemaBody) {
        Schema schema = new Schema.Parser().parse(notificationSchemaBody);
        List<TarEntryData> tarEntries = new LinkedList<>();

        tarEntries.add(createTarEntry(NOTIFICATION_HEADER,
                processHeaderTemplate("kaa_notification_definitions.hvm", schema, NOTIFICATION_NAMESPACE)));

        tarEntries.addAll(
                generateSourcesFromSchema(schema, NOTIFICATION_SOURCE_NAME_PATTERN, NOTIFICATION_NAMESPACE));

        return tarEntries;
    }
}