org.eclipse.winery.repository.export.CsarExporter.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.winery.repository.export.CsarExporter.java

Source

/*******************************************************************************
 * Copyright (c) 2012-2019 Contributors to the Eclipse Foundation
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0
 * which is available at https://www.apache.org/licenses/LICENSE-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
 *******************************************************************************/
package org.eclipse.winery.repository.export;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.SortedSet;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.eclipse.winery.accountability.AccountabilityManager;
import org.eclipse.winery.accountability.AccountabilityManagerFactory;
import org.eclipse.winery.accountability.exceptions.AccountabilityException;
import org.eclipse.winery.common.HashingUtil;
import org.eclipse.winery.common.RepositoryFileReference;
import org.eclipse.winery.common.Util;
import org.eclipse.winery.common.configuration.Environments;
import org.eclipse.winery.common.constants.MimeTypes;
import org.eclipse.winery.common.ids.GenericId;
import org.eclipse.winery.common.ids.IdNames;
import org.eclipse.winery.common.ids.admin.NamespacesId;
import org.eclipse.winery.common.ids.definitions.ArtifactTemplateId;
import org.eclipse.winery.common.ids.definitions.DefinitionsChildId;
import org.eclipse.winery.common.ids.definitions.ServiceTemplateId;
import org.eclipse.winery.common.version.VersionUtils;
import org.eclipse.winery.model.selfservice.Application;
import org.eclipse.winery.model.selfservice.Application.Options;
import org.eclipse.winery.model.selfservice.ApplicationOption;
import org.eclipse.winery.model.tosca.TArtifactReference;
import org.eclipse.winery.model.tosca.TArtifactTemplate;
import org.eclipse.winery.repository.Constants;
import org.eclipse.winery.repository.GitInfo;
import org.eclipse.winery.repository.backend.BackendUtils;
import org.eclipse.winery.repository.backend.IGenericRepository;
import org.eclipse.winery.repository.backend.IRepository;
import org.eclipse.winery.repository.backend.SelfServiceMetaDataUtils;
import org.eclipse.winery.repository.backend.constants.MediaTypes;
import org.eclipse.winery.repository.datatypes.ids.elements.DirectoryId;
import org.eclipse.winery.repository.datatypes.ids.elements.SelfServiceMetaDataId;
import org.eclipse.winery.repository.datatypes.ids.elements.ServiceTemplateSelfServiceFilesDirectoryId;
import org.eclipse.winery.repository.exceptions.RepositoryCorruptException;
import org.eclipse.winery.repository.export.entries.CsarEntry;
import org.eclipse.winery.repository.export.entries.DefinitionsBasedCsarEntry;
import org.eclipse.winery.repository.export.entries.DocumentBasedCsarEntry;
import org.eclipse.winery.repository.export.entries.RepositoryRefBasedCsarEntry;

import org.apache.commons.io.IOUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.eclipse.winery.model.csar.toscametafile.TOSCAMetaFileAttributes.CONTENT_TYPE;
import static org.eclipse.winery.model.csar.toscametafile.TOSCAMetaFileAttributes.CREATED_BY;
import static org.eclipse.winery.model.csar.toscametafile.TOSCAMetaFileAttributes.CSAR_VERSION;
import static org.eclipse.winery.model.csar.toscametafile.TOSCAMetaFileAttributes.ENTRY_DEFINITIONS;
import static org.eclipse.winery.model.csar.toscametafile.TOSCAMetaFileAttributes.HASH;
import static org.eclipse.winery.model.csar.toscametafile.TOSCAMetaFileAttributes.IMMUTABLE_ADDRESS;
import static org.eclipse.winery.model.csar.toscametafile.TOSCAMetaFileAttributes.NAME;
import static org.eclipse.winery.model.csar.toscametafile.TOSCAMetaFileAttributes.TOSCA_META_VERSION;

/**
 * This class exports a CSAR crawling from the the given GenericId. Currently, only ServiceTemplates are supported.
 * commons-compress is used as an output stream should be provided. An alternative implementation is to use Java7's Zip
 * File System Provider
 */
public class CsarExporter {

    public static final String PATH_TO_NAMESPACES_PROPERTIES = "winery/Namespaces.properties";
    public static final String PATH_TO_NAMESPACES_JSON = "winery/Namespaces.json";

    private static final Logger LOGGER = LoggerFactory.getLogger(CsarExporter.class);

    private static final String DEFINITONS_PATH_PREFIX = "Definitions/";
    private static final String WINERY_TEMP_DIR_PREFIX = "winerytmp";

    /**
     * Returns a unique name for the given definitions to be used as filename
     */
    private static String getDefinitionsName(IGenericRepository repository, DefinitionsChildId id) {
        // the prefix is globally unique and the id locally in a namespace
        // therefore a concatenation of both is also unique
        return repository.getNamespaceManager().getPrefix(id.getNamespace()) + "__" + id.getXmlId().getEncoded();
    }

    public static String getDefinitionsFileName(IGenericRepository repository, DefinitionsChildId id) {
        return CsarExporter.getDefinitionsName(repository, id) + Constants.SUFFIX_TOSCA_DEFINITIONS;
    }

    private static String getDefinitionsPathInsideCSAR(IGenericRepository repository, DefinitionsChildId id) {
        return CsarExporter.DEFINITONS_PATH_PREFIX + CsarExporter.getDefinitionsFileName(repository, id);
    }

    public CompletableFuture<String> writeCsarAndSaveManifestInProvenanceLayer(IRepository repository,
            DefinitionsChildId entryId, OutputStream out) throws IOException, RepositoryCorruptException,
            AccountabilityException, InterruptedException, ExecutionException {
        LocalDateTime start = LocalDateTime.now();
        Properties props = repository.getAccountabilityConfigurationManager().properties;
        AccountabilityManager accountabilityManager = AccountabilityManagerFactory.getAccountabilityManager(props);

        Map<String, Object> exportConfiguration = new HashMap<>();
        exportConfiguration.put(CsarExportConfiguration.INCLUDE_HASHES.name(), null);
        exportConfiguration.put(CsarExportConfiguration.STORE_IMMUTABLY.name(), null);

        String manifestString = this.writeCsar(repository, entryId, out, exportConfiguration);
        String qNameWithComponentVersionOnly = VersionUtils.getQNameWithComponentVersionOnly(entryId);
        LOGGER.debug("Preparing CSAR export (provenance) lasted {}",
                Duration.between(LocalDateTime.now(), start).toString());

        return accountabilityManager.storeFingerprint(qNameWithComponentVersionOnly, manifestString);
    }

    /**
     * Writes a complete CSAR containing all necessary things reachable from the given service template
     *
     * @param entryId the id of the service template to export
     * @param out     the output stream to write to
     * @return the TOSCA meta file for the generated Csar
     */
    public String writeCsar(IRepository repository, DefinitionsChildId entryId, OutputStream out,
            Map<String, Object> exportConfiguration) throws IOException, RepositoryCorruptException,
            InterruptedException, AccountabilityException, ExecutionException {
        CsarExporter.LOGGER.trace("Starting CSAR export with {}", entryId.toString());

        Map<CsarContentProperties, CsarEntry> refMap = new HashMap<>();
        ToscaExportUtil exporter = new ToscaExportUtil();
        ExportedState exportedState = new ExportedState();
        DefinitionsChildId currentId = entryId;
        Collection<DefinitionsChildId> referencedIds;

        // Process definitions and referenced files
        do {
            String definitionsPathInsideCSAR = CsarExporter.getDefinitionsPathInsideCSAR(repository, currentId);
            CsarContentProperties definitionsFileProperties = new CsarContentProperties(definitionsPathInsideCSAR);
            referencedIds = exporter.processTOSCA(repository, currentId, definitionsFileProperties, refMap,
                    exportConfiguration);

            // for each entryId add license and readme files (if they exist) to the refMap
            addLicenseAndReadmeFiles(repository, currentId, refMap);

            exportedState.flagAsExported(currentId);
            exportedState.flagAsExportRequired(referencedIds);

            currentId = exportedState.pop();
        } while (currentId != null);

        // if we export a ServiceTemplate, data for the self-service portal might exist
        if (entryId instanceof ServiceTemplateId) {
            ServiceTemplateId serviceTemplateId = (ServiceTemplateId) entryId;
            this.addSelfServiceMetaData(repository, serviceTemplateId, refMap);
            this.addSelfServiceFiles(repository, serviceTemplateId, refMap);
        }

        this.addNamespacePrefixes(repository, refMap);

        /* now, refMap contains all files to be added to the CSAR */

        // Calculate hashes for referenced files if necessary
        if (exportConfiguration.containsKey(CsarExportConfiguration.INCLUDE_HASHES.name())) {
            LOGGER.trace("Calculating checksum for {} files.", refMap.size());
            calculateFileHashes(refMap);
        }

        // Store referenced files in immutable file storage if necessary
        if (exportConfiguration.containsKey(CsarExportConfiguration.STORE_IMMUTABLY.name())) {
            try {
                LOGGER.trace("Storing {} files in the immutable file storage", refMap.size());
                immutablyStoreRefFiles(refMap, repository);
            } catch (InterruptedException | ExecutionException | AccountabilityException e) {
                LOGGER.error("Failed to store files in immutable storage. Reason: {}", e.getMessage());
                throw e;
            }
        }

        // Archive creation
        try (final ZipOutputStream zos = new ZipOutputStream(out)) {
            // write all referenced files
            for (Map.Entry<CsarContentProperties, CsarEntry> entry : refMap.entrySet()) {
                CsarContentProperties fileProperties = entry.getKey();
                CsarEntry ref = entry.getValue();
                CsarExporter.LOGGER.trace("Creating {}", fileProperties.getPathInsideCsar());

                if (ref instanceof RepositoryRefBasedCsarEntry
                        && ((RepositoryRefBasedCsarEntry) ref).getReference().getParent() instanceof DirectoryId) {
                    addArtifactTemplateToZipFile(zos, (RepositoryRefBasedCsarEntry) ref, fileProperties);
                } else {
                    addCsarEntryToArchive(zos, ref, fileProperties);
                }
            }

            // create manifest file and add it to archive
            return this.addManifest(repository, entryId, refMap, zos, exportConfiguration);
        }
    }

    private void calculateFileHashes(Map<CsarContentProperties, CsarEntry> files) {
        files.forEach((properties, entry) -> {
            try (InputStream is = entry.getInputStream()) {
                properties.setFileHash(HashingUtil.getChecksum(is, HASH));
            } catch (IOException | NoSuchAlgorithmException e) {
                LOGGER.error("Failed to calculate hash for {}. Reason: {}.", properties.getPathInsideCsar(),
                        e.getMessage());
            }
        });
    }

    /**
     * Stores all files listed in the map in the immutable file storage, and updates the CsarContentProperties of each
     * file to contain its address in the aforementioned storage.
     *
     * @param filesToStore a map of the CsarContentProperties of all files to be stored in the CSAR and their contents.
     */
    private void immutablyStoreRefFiles(Map<CsarContentProperties, CsarEntry> filesToStore, IRepository repository)
            throws AccountabilityException, ExecutionException, InterruptedException, IOException {
        Properties props = repository.getAccountabilityConfigurationManager().properties;
        AccountabilityManager manager = AccountabilityManagerFactory.getAccountabilityManager(props);
        Map<String, InputStream> filesMap = new HashMap<>();

        for (Map.Entry<CsarContentProperties, CsarEntry> entry : filesToStore.entrySet()) {
            filesMap.put(entry.getKey().getPathInsideCsar(), entry.getValue().getInputStream());
        }

        // store all files in immutable storage (already stored files will get their same old address)
        Map<String, String> addressMap = manager.storeState(filesMap).get();

        filesToStore.keySet().forEach((CsarContentProperties properties) -> {
            properties.setImmutableAddress(addressMap.get(properties.getPathInsideCsar()));
        });
    }

    /**
     * Special handling for artifact template directories source and files
     *
     * @param zos            Output stream for the archive that should contain the file
     * @param csarEntry      Reference to the file that should be added to the archive
     * @param fileProperties Describing the path to the file inside the archive
     * @throws IOException thrown when the temporary directory can not be created
     */
    private void addArtifactTemplateToZipFile(ZipOutputStream zos, RepositoryRefBasedCsarEntry csarEntry,
            CsarContentProperties fileProperties) throws IOException {
        GitInfo gitInfo = BackendUtils.getGitInformation((DirectoryId) csarEntry.getReference().getParent());

        if (gitInfo == null) {
            addCsarEntryToArchive(zos, csarEntry, fileProperties);
            return;
        }

        // TODO: This is not quite correct. The files should reside checked out at "source/"
        // TODO: Hash all these git files (to be included in the provenance)
        Path tempDir = Files.createTempDirectory(WINERY_TEMP_DIR_PREFIX);
        try {
            Git git = Git.cloneRepository().setURI(gitInfo.URL).setDirectory(tempDir.toFile()).call();
            git.checkout().setName(gitInfo.BRANCH).call();
            String path = "artifacttemplates/"
                    + Util.URLencode(((ArtifactTemplateId) csarEntry.getReference().getParent().getParent())
                            .getQName().getNamespaceURI())
                    + "/" + ((ArtifactTemplateId) csarEntry.getReference().getParent().getParent()).getQName()
                            .getLocalPart()
                    + "/files/";
            TArtifactTemplate template = BackendUtils
                    .getTArtifactTemplate((DirectoryId) csarEntry.getReference().getParent());
            addWorkingTreeToArchive(zos, template, tempDir, path);
        } catch (GitAPIException e) {
            CsarExporter.LOGGER
                    .error(String.format("Error while cloning repo: %s / %s", gitInfo.URL, gitInfo.BRANCH), e);
        } finally {
            deleteDirectory(tempDir);
        }
    }

    /**
     * Adds a file to an archive
     *
     * @param zos                   Output stream of the archive
     * @param csarEntry             Reference to the file that should be added to the archive
     * @param csarContentProperties Describing the path inside the archive to the file
     */
    private void addCsarEntryToArchive(ZipOutputStream zos, CsarEntry csarEntry,
            CsarContentProperties csarContentProperties) {
        try (InputStream is = csarEntry.getInputStream()) {
            zos.putNextEntry(new ZipEntry(csarContentProperties.getPathInsideCsar()));
            IOUtils.copy(is, zos);
            zos.closeEntry();
        } catch (Exception e) {
            CsarExporter.LOGGER.error("Could not copy file content to ZIP outputstream", e);
        }
    }

    /**
     * Deletes a directory recursively
     *
     * @param path Path to the directory that should be deleted
     */
    private void deleteDirectory(Path path) {
        if (Files.isDirectory(path)) {
            try (DirectoryStream<Path> s = Files.newDirectoryStream(path)) {
                for (Path p : s) {
                    deleteDirectory(p);
                }
                Files.delete(path);
            } catch (IOException e) {
                CsarExporter.LOGGER.error("Error iterating directory " + path.toAbsolutePath(), e);
            }
        } else {
            try {
                Files.delete(path);
            } catch (IOException e) {
                CsarExporter.LOGGER.error("Error deleting file " + path.toAbsolutePath(), e);
            }
        }
    }

    /**
     * Adds a working tree to an archive
     *
     * @param zos         Output stream of the archive
     * @param template    Template of the artifact
     * @param rootDir     The root of the working tree
     * @param archivePath The path inside the archive to the working tree
     */
    private void addWorkingTreeToArchive(ZipOutputStream zos, TArtifactTemplate template, Path rootDir,
            String archivePath) {
        addWorkingTreeToArchive(rootDir.toFile(), zos, template, rootDir, archivePath);
    }

    /**
     * Adds a working tree to an archive
     *
     * @param file        The current directory to add
     * @param zos         Output stream of the archive
     * @param template    Template of the artifact
     * @param rootDir     The root of the working tree
     * @param archivePath The path inside the archive to the working tree
     */
    private void addWorkingTreeToArchive(File file, ZipOutputStream zos, TArtifactTemplate template, Path rootDir,
            String archivePath) {
        if (file.isDirectory()) {
            if (file.getName().equals(".git")) {
                return;
            }
            File[] files = file.listFiles();
            if (files != null) {
                for (File f : files) {
                    addWorkingTreeToArchive(f, zos, template, rootDir, archivePath);
                }
            }
        } else {
            boolean foundInclude = false;
            boolean included = false;
            boolean excluded = false;
            for (TArtifactReference artifactReference : template.getArtifactReferences().getArtifactReference()) {
                for (Object includeOrExclude : artifactReference.getIncludeOrExclude()) {
                    if (includeOrExclude instanceof TArtifactReference.Include) {
                        foundInclude = true;
                        TArtifactReference.Include include = (TArtifactReference.Include) includeOrExclude;
                        String reference = artifactReference.getReference();
                        if (reference.endsWith("/")) {
                            reference += include.getPattern();
                        } else {
                            reference += "/" + include.getPattern();
                        }
                        reference = reference.substring(1);
                        included |= BackendUtils.isGlobMatch(reference, rootDir.relativize(file.toPath()));
                    } else if (includeOrExclude instanceof TArtifactReference.Exclude) {
                        TArtifactReference.Exclude exclude = (TArtifactReference.Exclude) includeOrExclude;
                        String reference = artifactReference.getReference();
                        if (reference.endsWith("/")) {
                            reference += exclude.getPattern();
                        } else {
                            reference += "/" + exclude.getPattern();
                        }
                        reference = reference.substring(1);
                        excluded |= BackendUtils.isGlobMatch(reference, rootDir.relativize(file.toPath()));
                    }
                }
            }

            if ((!foundInclude || included) && !excluded) {
                try (InputStream is = new FileInputStream(file)) {
                    ZipEntry archiveEntry = new ZipEntry(
                            archivePath + rootDir.relativize(Paths.get(file.getAbsolutePath())));
                    zos.putNextEntry(archiveEntry);
                    IOUtils.copy(is, zos);
                    zos.closeEntry();
                } catch (Exception e) {
                    CsarExporter.LOGGER.error("Could not copy file to ZIP outputstream", e);
                }
            }
        }
    }

    /**
     * Writes the configured mapping namespaceprefix -> namespace to the archive
     * <p>
     * This is kind of a quick hack. TODO: during the import, the prefixes should be extracted using JAXB and stored in
     * the NamespacesResource
     */
    private void addNamespacePrefixes(IRepository repository, Map<CsarContentProperties, CsarEntry> refMap)
            throws IOException {
        // ensure that the namespaces are saved as json
        SortedSet<RepositoryFileReference> references = repository.getContainedFiles(new NamespacesId());

        references.forEach(repositoryFileReference -> {
            if (repositoryFileReference.getFileName().toLowerCase().endsWith(Constants.SUFFIX_JSON)) {
                CsarContentProperties csarContentProperties = new CsarContentProperties(
                        CsarExporter.PATH_TO_NAMESPACES_JSON);
                refMap.put(csarContentProperties, new RepositoryRefBasedCsarEntry(repositoryFileReference));
            }
        });
    }

    /**
     * Adds all self service meta data to the targetDir
     *
     * @param repository the repository to work from
     * @param entryId    the service template to export for
     * @param targetDir  the directory in the CSAR where to put the content to
     * @param refMap     is used later to create the CSAR
     */
    private void addSelfServiceMetaData(IRepository repository, ServiceTemplateId entryId, String targetDir,
            Map<CsarContentProperties, CsarEntry> refMap) throws IOException {
        final SelfServiceMetaDataId selfServiceMetaDataId = new SelfServiceMetaDataId(entryId);

        // This method is also called if the directory SELFSERVICE-Metadata exists without content and even if the directory does not exist at all,
        // but the ServiceTemplate itself exists.
        // The current assumption is that this is enough for an existence.
        // Thus, we have to take care of the case of an empty directory and add a default data.xml
        SelfServiceMetaDataUtils.ensureDataXmlExists(selfServiceMetaDataId);

        CsarContentProperties csarContentProperties = new CsarContentProperties(targetDir + "data.xml");
        refMap.put(csarContentProperties,
                new RepositoryRefBasedCsarEntry(SelfServiceMetaDataUtils.getDataXmlRef(selfServiceMetaDataId)));

        // The schema says that the images have to exist
        // However, at a quick modeling, there might be no images
        // Therefore, we check for existence
        final RepositoryFileReference iconJpgRef = SelfServiceMetaDataUtils.getIconJpgRef(selfServiceMetaDataId);
        if (repository.exists(iconJpgRef)) {
            csarContentProperties = new CsarContentProperties(targetDir + "icon.jpg");
            refMap.put(csarContentProperties, new RepositoryRefBasedCsarEntry(iconJpgRef));
        }
        final RepositoryFileReference imageJpgRef = SelfServiceMetaDataUtils.getImageJpgRef(selfServiceMetaDataId);
        if (repository.exists(imageJpgRef)) {
            csarContentProperties = new CsarContentProperties(targetDir + "image.jpg");
            refMap.put(csarContentProperties, new RepositoryRefBasedCsarEntry(imageJpgRef));
        }

        Application application = SelfServiceMetaDataUtils.getApplication(selfServiceMetaDataId);
        // set to true only if changes are applied to application
        boolean isApplicationChanged = false;

        if (application.getCsarName() != null) {
            // clear CSAR name as this may change.
            application.setCsarName(null);
            isApplicationChanged = true;
        }

        if (application.getVersion() == null || !application.getVersion().equals("1.0")) {
            // hack for the OpenTOSCA container to display something
            application.setVersion("1.0");
            isApplicationChanged = true;
        }
        List<String> authors = application.getAuthors();
        if (authors.isEmpty()) {
            authors.add("Winery");
            isApplicationChanged = true;
        }

        if (isApplicationChanged) {
            // make the patches to data.xml permanent
            try {
                BackendUtils.persist(application, SelfServiceMetaDataUtils.getDataXmlRef(selfServiceMetaDataId),
                        MediaTypes.MEDIATYPE_TEXT_XML);
            } catch (IOException e) {
                LOGGER.error("Could not persist patches to data.xml", e);
            }
        }

        Options options = application.getOptions();
        if (options != null) {
            SelfServiceMetaDataId id = new SelfServiceMetaDataId(entryId);
            for (ApplicationOption option : options.getOption()) {
                String url = option.getIconUrl();
                if (Util.isRelativeURI(url)) {
                    putRefIntoRefMap(targetDir, refMap, repository, id, url);
                }
                url = option.getPlanInputMessageUrl();
                if (Util.isRelativeURI(url)) {
                    putRefIntoRefMap(targetDir, refMap, repository, id, url);
                }
            }
        }
    }

    private void putRefIntoRefMap(String targetDir, Map<CsarContentProperties, CsarEntry> refMap,
            IRepository repository, GenericId id, String fileName) {
        RepositoryFileReference ref = new RepositoryFileReference(id, fileName);
        if (repository.exists(ref)) {
            CsarContentProperties csarContentProperties = new CsarContentProperties(targetDir + fileName);
            refMap.put(csarContentProperties, new RepositoryRefBasedCsarEntry(ref));
        } else {
            CsarExporter.LOGGER.error("Data corrupt: pointing to non-existent file " + ref);
        }
    }

    private void addLicenseAndReadmeFiles(IRepository repository, DefinitionsChildId entryId,
            Map<CsarContentProperties, CsarEntry> refMap) {
        final RepositoryFileReference licenseRef = new RepositoryFileReference(entryId,
                Constants.LICENSE_FILE_NAME);
        if (repository.exists(licenseRef)) {
            refMap.put(new CsarContentProperties(BackendUtils.getPathInsideRepo(licenseRef)),
                    new RepositoryRefBasedCsarEntry(licenseRef));
        }

        final RepositoryFileReference readmeRef = new RepositoryFileReference(entryId, Constants.README_FILE_NAME);
        if (repository.exists(readmeRef)) {
            refMap.put(new CsarContentProperties(BackendUtils.getPathInsideRepo(readmeRef)),
                    new RepositoryRefBasedCsarEntry(readmeRef));
        }
    }

    private void addSelfServiceMetaData(IRepository repository, ServiceTemplateId serviceTemplateId,
            Map<CsarContentProperties, CsarEntry> refMap) throws IOException {
        SelfServiceMetaDataId id = new SelfServiceMetaDataId(serviceTemplateId);
        // We add the self-service information regardless of the existence. - i.e., no "if (repository.exists(id)) {"
        // This ensures that the name of the application is
        // add everything in the root of the CSAR
        String targetDir = Constants.DIRNAME_SELF_SERVICE_METADATA + "/";
        addSelfServiceMetaData(repository, serviceTemplateId, targetDir, refMap);
    }

    private void addSelfServiceFiles(IRepository repository, ServiceTemplateId serviceTemplateId,
            Map<CsarContentProperties, CsarEntry> refMap) {
        ServiceTemplateSelfServiceFilesDirectoryId selfServiceFilesDirectoryId = new ServiceTemplateSelfServiceFilesDirectoryId(
                serviceTemplateId);
        repository.getContainedFiles(selfServiceFilesDirectoryId).forEach(repositoryFileReference -> {
            String file = IdNames.SELF_SERVICE_PORTAL_FILES + "/"
                    + BackendUtils.getFilenameAndSubDirectory(repositoryFileReference);
            CsarContentProperties csarContentProperties = new CsarContentProperties(file);
            refMap.put(csarContentProperties, new RepositoryRefBasedCsarEntry(repositoryFileReference));
        });
    }

    private String addManifest(IRepository repository, DefinitionsChildId id,
            Map<CsarContentProperties, CsarEntry> refMap, ZipOutputStream out,
            Map<String, Object> exportConfiguration) throws IOException {
        String entryDefinitionsReference = CsarExporter.getDefinitionsPathInsideCSAR(repository, id);

        out.putNextEntry(new ZipEntry("TOSCA-Metadata/TOSCA.meta"));
        StringBuilder stringBuilder = new StringBuilder();

        // Setting Versions
        stringBuilder.append(TOSCA_META_VERSION).append(": 1.0").append("\n");
        stringBuilder.append(CSAR_VERSION).append(": 1.0").append("\n");
        stringBuilder.append(CREATED_BY).append(": Winery ").append(Environments.getVersion()).append("\n");

        // Winery currently is unaware of tDefinitions, therefore, we use the
        // name of the service template
        stringBuilder.append(ENTRY_DEFINITIONS).append(": ").append(entryDefinitionsReference).append("\n");
        stringBuilder.append("\n");

        assert (refMap.keySet().stream()
                .anyMatch(fileProperties -> fileProperties.getPathInsideCsar().equals(entryDefinitionsReference)));

        // Setting other files, mainly files belonging to artifacts
        for (Map.Entry<CsarContentProperties, CsarEntry> item : refMap.entrySet()) {
            final CsarEntry csarEntry = item.getValue();
            final CsarContentProperties fileProperties = item.getKey();

            stringBuilder.append(NAME).append(": ").append(fileProperties.getPathInsideCsar()).append("\n");

            String mimeType = "";

            if (csarEntry instanceof DocumentBasedCsarEntry) {
                mimeType = MimeTypes.MIMETYPE_XSD;
            } else if (csarEntry instanceof DefinitionsBasedCsarEntry) {
                mimeType = MimeTypes.MIMETYPE_TOSCA_DEFINITIONS;
            } else {
                mimeType = repository.getMimeType(((RepositoryRefBasedCsarEntry) csarEntry).getReference());
            }

            stringBuilder.append(CONTENT_TYPE).append(": ").append(mimeType).append("\n");

            if (exportConfiguration.containsKey(CsarExportConfiguration.INCLUDE_HASHES.name())
                    && Objects.nonNull(fileProperties.getFileHash())) {
                stringBuilder.append(HASH).append(": ").append(fileProperties.getFileHash()).append("\n");
            }

            if (exportConfiguration.containsKey(CsarExportConfiguration.STORE_IMMUTABLY.name())
                    && Objects.nonNull(fileProperties.getImmutableAddress())) {
                stringBuilder.append(IMMUTABLE_ADDRESS).append(": ").append(fileProperties.getImmutableAddress())
                        .append("\n");
            }

            stringBuilder.append("\n");
        }

        String manifestString = stringBuilder.toString();
        out.write(manifestString.getBytes());
        out.closeEntry();

        return manifestString;
    }
}