private void doGenerateAssembly()

in profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java [907:1201]


    private void doGenerateAssembly() throws Exception {
        LOGGER.info("Generating Karaf assembly: " + homeDirectory);

        //
        // Create download manager - combination of pax-url-aether and a resolver wrapper that may
        // alter the way pax-url-aether resolver works
        //
        MavenResolver resolver = createMavenResolver();
        manager = new CustomDownloadManager(resolver, executor, null, translatedUrls);
        this.resolver = new ResolverImpl(new Slf4jResolverLog(LOGGER), resolverParallelism);

        //
        // Unzip KARs
        //
        LOGGER.info("Unzipping kars");
        Downloader downloader = manager.createDownloader();
        for (String kar : kars.keySet()) {
            downloader.download(kar, null);
        }
        downloader.await();
        // each KAR is extracted and all features XML repositories found there are added to the same
        // stage as the KAR and with the same "add all" flag as the KAR itself
        for (String karUri : kars.keySet()) {
            LOGGER.info("   processing KAR: " + karUri);
            Kar kar = new Kar(manager.getProviders().get(karUri).getFile().toURI());
            kar.extract(systemDirectory.toFile(), homeDirectory.toFile());
            RepositoryInfo info = kars.get(karUri);
            for (URI repositoryUri : kar.getFeatureRepos()) {
                LOGGER.info("      found repository: " + repositoryUri);
                repositories.put(repositoryUri.toString(), info);
            }
        }

        //
        // Load profiles
        //
        LOGGER.info("Loading profiles from:");
        profilesUris.forEach(p -> LOGGER.info("   " + p));
        allProfiles = loadExternalProfiles(profilesUris);
        if (allProfiles.size() > 0) {
            StringBuilder sb = new StringBuilder();
            LOGGER.info("   Found profiles: " + String.join(", ", allProfiles.keySet()));
        }

        // Generate initial profile to collect overrides and blacklisting instructions
        Profile initialProfile = ProfileBuilder.Factory.create("initial")
                .setParents(new ArrayList<>(profiles.keySet()))
                .getProfile();
        Profile initialOverlay = Profiles.getOverlay(initialProfile, allProfiles, environment);
        Profile initialEffective = Profiles.getEffective(initialOverlay, false);

        //
        // Handle blacklist - we'll use SINGLE instance of Blacklist for all further downloads
        //
        blacklist = processBlacklist(initialEffective);

        //
        // Configure blacklisting and overriding features processor
        //

        boolean needFeaturesProcessorFileCopy = false;
        String existingProcessorDefinitionURI = null;
        String additionalProcessorDefinitionURI = null;
        Path existingProcessorDefinition = etcDirectory.resolve("org.apache.karaf.features.xml");
        if (existingProcessorDefinition.toFile().isFile()) {
            existingProcessorDefinitionURI = existingProcessorDefinition.toFile().toURI().toString();
            LOGGER.info("Found existing features processor configuration: {}", homeDirectory.relativize(existingProcessorDefinition));
        }
        if (featuresProcessingLocation != null && featuresProcessingLocation.toFile().isFile()
                && !featuresProcessingLocation.equals(existingProcessorDefinition)) {
            if (existingProcessorDefinitionURI != null) {
                LOGGER.info("Found additional features processor configuration: {}", homeDirectory.relativize(featuresProcessingLocation));
                additionalProcessorDefinitionURI = featuresProcessingLocation.toFile().toURI().toString();
            } else {
                LOGGER.info("Found features processor configuration: {}", homeDirectory.relativize(featuresProcessingLocation));
                existingProcessorDefinitionURI = featuresProcessingLocation.toFile().toURI().toString();
            }
            // when there are no other (configured via Maven for example) processing instructions (e.g., blacklisting)
            // we don't have to generate this file and may take original content
            needFeaturesProcessorFileCopy = true;
        }

        // now we can configure blacklisting features processor which may have already defined (in XML)
        // configuration for bundle replacements or feature overrides.
        FeaturesProcessorImpl processor = new FeaturesProcessorImpl(existingProcessorDefinitionURI, null, blacklist, new HashSet<>());

        if (additionalProcessorDefinitionURI != null) {
            FeaturesProcessorImpl processor2 = new FeaturesProcessorImpl(additionalProcessorDefinitionURI, null, new Blacklist(), new HashSet<>());
            FeaturesProcessing existingConfiguration = processor.getInstructions();
            FeaturesProcessing additionalConfiguration = processor2.getInstructions();
            additionalConfiguration.getBlacklistedRepositories()
                    .forEach(br -> existingConfiguration.getBlacklistedRepositories().add(br));
            additionalConfiguration.getBlacklistedRepositoryLocationPatterns()
                    .forEach(lp -> existingConfiguration.getBlacklistedRepositoryLocationPatterns().add(lp));
            additionalConfiguration.getBlacklistedFeatures()
                    .forEach(bf -> existingConfiguration.getBlacklistedFeatures().add(bf));
            additionalConfiguration.getBlacklistedBundles()
                    .forEach(bb -> existingConfiguration.getBlacklistedBundles().add(bb));
            additionalConfiguration.getFeatureReplacements().getReplacements()
                    .forEach(fr -> existingConfiguration.getFeatureReplacements().getReplacements().add(fr));
            additionalConfiguration.getBundleReplacements().getOverrideBundles()
                    .forEach(br -> existingConfiguration.getBundleReplacements().getOverrideBundles().add(br));
            additionalConfiguration.getOverrideBundleDependency().getRepositories()
                    .forEach(r -> existingConfiguration.getOverrideBundleDependency().getRepositories().add(r));
            additionalConfiguration.getOverrideBundleDependency().getFeatures()
                    .forEach(f -> existingConfiguration.getOverrideBundleDependency().getFeatures().add(f));
            additionalConfiguration.getOverrideBundleDependency().getBundles()
                    .forEach(b -> existingConfiguration.getOverrideBundleDependency().getBundles().add(b));
        }

        // add overrides from initialProfile
        Set<String> overrides = processOverrides(initialEffective.getOverrides());
        processor.addOverrides(overrides);

        //
        // Propagate feature installation from repositories
        //
        LOGGER.info("Loading repositories");
        Map<String, Features> karRepositories = loadRepositories(manager, repositories.keySet(), false, processor);
        for (String repo : repositories.keySet()) {
            RepositoryInfo info = repositories.get(repo);
            if (info.addAll) {
                LOGGER.info("   adding all non-blacklisted features from repository: " + repo + " (stage: " + info.stage + ")");
                for (Feature feature : karRepositories.get(repo).getFeature()) {
                    if (feature.isBlacklisted()) {
                        LOGGER.info("      feature {}/{} is blacklisted - skipping.", feature.getId(), feature.getVersion());
                    } else {
                        features.put(feature.getId(), info.stage);
                    }
                }
            }
        }

        //
        // Generate profiles. If user has configured additional profiles, they'll be used as parents
        // of the generated ones.
        //
        Profile startupProfile = generateProfile(Stage.Startup, profiles, repositories, features, bundles);
        allProfiles.put(startupProfile.getId(), startupProfile);

        // generated startup profile should be used (together with configured startup and boot profiles) as parent
        // of the generated boot profile - similar visibility rule (boot stage requires startup stage) is applied
        // for repositories and features
        profiles.put(startupProfile.getId(), Stage.Boot);
        Profile bootProfile = generateProfile(Stage.Boot, profiles, repositories, features, bundles);
        allProfiles.put(bootProfile.getId(), bootProfile);

        Profile installedProfile = generateProfile(Stage.Installed, profiles, repositories, features, bundles);
        allProfiles.put(installedProfile.getId(), installedProfile);

        //
        // Compute "overlay" profile - a single profile with all parent profiles included (when there's the same
        // file in both profiles, parent profile's version has lower priority)
        //
        ProfileBuilder builder = ProfileBuilder.Factory.create(UUID.randomUUID().toString())
                .setParents(Arrays.asList(startupProfile.getId(), bootProfile.getId(), installedProfile.getId()));
        config.forEach((k ,v) -> builder.addConfiguration(Profile.INTERNAL_PID, Profile.CONFIG_PREFIX + k, v));
        system.forEach((k ,v) -> builder.addConfiguration(Profile.INTERNAL_PID, Profile.SYSTEM_PREFIX + k, v));
        // profile with all the parents configured and stage-agnostic blacklisting configuration added
        blacklistedRepositoryURIs.forEach(builder::addBlacklistedRepository);
        blacklistedFeatureIdentifiers.forEach(builder::addBlacklistedFeature);
        blacklistedBundleURIs.forEach(builder::addBlacklistedBundle);
        // final profilep
        Profile overallProfile = builder.getProfile();

        // profile with parents included and "flattened" using inheritance rules (child files overwrite parent
        // files and child PIDs are merged with parent PIDs and same properties are taken from child profiles)
        Profile overallOverlay = Profiles.getOverlay(overallProfile, allProfiles, environment);

        // profile with property placeholders resolved or left unchanged (if there's no property value available,
        // so property placeholders are preserved - like ${karaf.base})
        Profile overallEffective = Profiles.getEffective(overallOverlay, false);

        if (writeProfiles) {
            Path profiles = etcDirectory.resolve("profiles");
            LOGGER.info("Adding profiles to {}", homeDirectory.relativize(profiles));
            allProfiles.forEach((id, profile) -> {
                try {
                    Profiles.writeProfile(profiles, profile);
                } catch (IOException e) {
                    LOGGER.warn("Problem writing profile {}: {}", id, e.getMessage());
                }
            });
        }

        manager = new CustomDownloadManager(resolver, executor, overallEffective, translatedUrls);

//        Hashtable<String, String> profileProps = new Hashtable<>(overallEffective.getConfiguration(ORG_OPS4J_PAX_URL_MVN_PID));
//        final Map<String, String> properties = new HashMap<>();
//        properties.put("karaf.default.repository", "system");
//        InterpolationHelper.performSubstitution(profileProps, properties::get, false, false, true);

        //
        // Write config and system properties
        //
        LOGGER.info("Configuring etc/config.properties and etc/system.properties");

        Path configPropertiesPath = etcDirectory.resolve("config.properties");
        Properties configProperties = new Properties(configPropertiesPath.toFile());
        configProperties.putAll(overallEffective.getConfig());
        configProperties.save();

        Path systemPropertiesPath = etcDirectory.resolve("system.properties");
        Properties systemProperties = new Properties(systemPropertiesPath.toFile());
        systemProperties.putAll(overallEffective.getSystem());
        systemProperties.save();

        //
        // Download libraries
        //
        // TODO: handle karaf 2.x and 3.x libraries
        downloader = manager.createDownloader();
        LOGGER.info("Downloading libraries for generated profiles");
        downloadLibraries(downloader, configProperties, overallEffective.getLibraries(), "");
        LOGGER.info("Downloading additional libraries");
        downloadLibraries(downloader, configProperties, libraries, "");
        downloader.await();

        // Reformat clauses
        reformatClauses(configProperties, Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA);
        reformatClauses(configProperties, Constants.FRAMEWORK_BOOTDELEGATION);
        configProperties.save();

        //
        // Write all configuration files
        //
        LOGGER.info("Writing configurations");
        for (Map.Entry<String, byte[]> config : overallEffective.getFileConfigurations().entrySet()) {
            Path configFile = etcDirectory.resolve(config.getKey());
            if (Files.exists(configFile)) {
                LOGGER.info("   not changing existing config file: {}", homeDirectory.relativize(configFile));
            } else {
                LOGGER.info("   adding config file: {}", homeDirectory.relativize(configFile));
                Files.createDirectories(configFile.getParent());
                Files.write(configFile, config.getValue());
            }
        }

        if (processor.hasInstructions()) {
            Path featuresProcessingXml = etcDirectory.resolve("org.apache.karaf.features.xml");
            if (hasOwnInstructions() || overrides.size() > 0) {
                // just generate new etc/org.apache.karaf.features.xml file (with external config + builder config)
                try (FileOutputStream fos = new FileOutputStream(featuresProcessingXml.toFile())) {
                    LOGGER.info("Generating features processor configuration: {}", homeDirectory.relativize(featuresProcessingXml));
                    processor.writeInstructions(fos);
                }
            } else if (needFeaturesProcessorFileCopy) {
                // we may simply copy configured features processor XML configuration
                LOGGER.info("Copying features processor configuration: {} -> {}", homeDirectory.relativize(featuresProcessingLocation), homeDirectory.relativize(featuresProcessingXml));
                Files.copy(featuresProcessingLocation, featuresProcessingXml, StandardCopyOption.REPLACE_EXISTING);
            }
        }

        //
        // Startup stage
        //
        Profile startupEffective = startupStage(startupProfile, processor);

        //
        // Boot stage
        //
        Set<Feature> allBootFeatures = bootStage(bootProfile, startupEffective, processor);

        //
        // Installed stage
        //
        Set<Feature> allInstalledFeatures = installStage(installedProfile, allBootFeatures, processor);

        // 'improve' configuration files.
        if (propertyEdits != null) {
            KarafPropertiesEditor editor = new KarafPropertiesEditor();
            editor.setInputEtc(etcDirectory.toFile())
            .setOutputEtc(etcDirectory.toFile())
            .setEdits(propertyEdits);
            editor.run();
        }

        if (generateConsistencyReport != null) {
            File directory = new File(generateConsistencyReport);
            if (directory.isFile()) {
                LOGGER.warn("Can't generate consistency report into {} - it's not a directory", generateConsistencyReport);
            } else {
                if (!directory.exists()) {
                    directory.mkdirs();
                }
                if (directory.isDirectory()) {
                    LOGGER.info("Writing bundle report");
                    generateConsistencyReport(karRepositories, allInstalledFeatures, installedProfile, new File(directory, "bundle-report.xml"));
                    Files.copy(getClass().getResourceAsStream("/bundle-report.xslt"),
                            directory.toPath().resolve("bundle-report.xslt"),
                            StandardCopyOption.REPLACE_EXISTING);
                }
            }
        }
    }