| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.apache.karaf.tooling; |
| |
| import javax.xml.namespace.QName; |
| import javax.xml.stream.XMLInputFactory; |
| import javax.xml.stream.XMLStreamException; |
| import javax.xml.stream.XMLStreamReader; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.nio.file.Files; |
| import java.nio.file.attribute.PosixFilePermissions; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.jar.JarFile; |
| import java.util.jar.Manifest; |
| import java.util.stream.Collectors; |
| import java.util.zip.ZipFile; |
| |
| import org.apache.karaf.profile.assembly.Builder; |
| import org.apache.karaf.tooling.utils.IoUtils; |
| import org.apache.karaf.tooling.utils.MavenUtil; |
| import org.apache.karaf.tooling.utils.MojoSupport; |
| import org.apache.karaf.tooling.utils.ReactorMavenResolver; |
| import org.apache.karaf.tools.utils.model.KarafPropertyEdits; |
| import org.apache.karaf.tools.utils.model.io.stax.KarafPropertyInstructionsModelStaxReader; |
| import org.apache.karaf.util.Version; |
| import org.apache.maven.artifact.Artifact; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugin.MojoFailureException; |
| import org.apache.maven.plugins.annotations.Component; |
| import org.apache.maven.plugins.annotations.LifecyclePhase; |
| import org.apache.maven.plugins.annotations.Mojo; |
| import org.apache.maven.plugins.annotations.Parameter; |
| import org.apache.maven.plugins.annotations.ResolutionScope; |
| import org.apache.maven.project.MavenProject; |
| import org.eclipse.aether.repository.WorkspaceReader; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.launch.FrameworkFactory; |
| |
| /** |
| * Creates a customized Karaf distribution by installing features and setting up |
| * configuration files. |
| * |
| * <p>The plugin gets features from feature.xml files and KAR |
| * archives declared as dependencies or as files configured with the |
| * [startup|boot|installed]Respositories parameters. It picks up other files, such as config files, |
| * from ${project.build.directory}/classes. Thus, a file in src/main/resources/etc |
| * will be copied by the resource plugin to ${project.build.directory}/classes/etc, |
| * and then added to the assembly by this goal. |
| */ |
| @Mojo(name = "assembly", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME, threadSafe = true) |
| public class AssemblyMojo extends MojoSupport { |
| |
| /** |
| * Base directory used to overwrite resources in generated assembly after the build (resource directory). |
| */ |
| @Parameter(defaultValue = "${project.basedir}/src/main/resources/assembly") |
| protected File sourceDirectory; |
| |
| /** |
| * Base directory used to copy the resources during the build (working directory). |
| */ |
| @Parameter(defaultValue = "${project.build.directory}/assembly") |
| protected File workDirectory; |
| |
| /** |
| * Optional location for custom features processing XML configuration |
| * (<code>etc/org.apache.karaf.features.cfg</code>) |
| */ |
| @Parameter |
| protected File featuresProcessing; |
| |
| /* |
| * There are three builder stages related to maven dependency scopes: |
| * - Stage.Startup : scope=compile |
| * - Stage.Boot : scope=runtime |
| * - Stage.Installed : scope=provided |
| * There's special category not related to stage - Blacklisted |
| * |
| * There are five kinds of artifacts/dependencies that may go into any of the above stages/categories/scopes: |
| * - kars: maven artifacts with "kar" type |
| * - repositories: maven artifacts with "features" classifier |
| * - features: Karaf feature names (name[/version]) |
| * - bundles: maven artifacts with "jar" or "bundle" type |
| * - profiles: directories with Karaf 4 profiles |
| * (Not all artifacts/dependencies may be connected with every stage/category/scope.) |
| * |
| * Blacklisting: |
| * - kars: there are no blacklisted kars |
| * - repositories: won't be processed at all (also affects transitive repositories) |
| * - features: will be removed from JAXB model of features XML after loading |
| * - bundles: will be removed from features of JAXB model after loading |
| * - profiles: will be removed |
| * |
| * Stage.Startup: |
| * - bundles: will be put to etc/startup.properties |
| * - features: their bundles will be put to etc/startup.properties |
| * - repositories: will be used to resolve startup bundles/feature before adding them to etc/startup.properties |
| * - kars: unpacked to assembly, detected features XML repositories added as Stage.Startup repositories |
| * |
| * Stage.Boot: |
| * - bundles: special etc/<UUID>.xml features XML file will be created with <UUID> feature. |
| * etc/org.apacha.karaf.features.cfg will have this features XML file in featuresRepositories property and |
| * the feature itself in featuresBoot property |
| * - features: will be added to etc/org.apacha.karaf.features.cfg file, featuresBoot property |
| * also features from Stage.Startup will be used here. |
| * - repositories: will be added to etc/org.apacha.karaf.features.cfg file, featuresRepositories property |
| * also repositories from Stage.Startup will be used here. |
| * - kars: unpacked to assembly, detected features XML repositories added as Stage.Boot repositories |
| * |
| * Stage.Installed: |
| * - bundles: will be copied to system/ |
| * - features: their bundles and config files will be copied to system/ |
| * - repositories: will be used to find Stage.Installed features |
| * also repositories from Stage.Boot will be searched for Stage.Installed features |
| * - kars: unpacked to assembly, detected features XML repositories added as Stage.Installed repositories |
| */ |
| |
| /** |
| * For given stage (startup, boot, install) if there are no stage-specific features and profiles, all features |
| * from stage-specific repositories will be used. |
| */ |
| @Parameter(defaultValue = "true") |
| protected boolean installAllFeaturesByDefault = true; |
| |
| /** |
| * An environment identifier that may be used to select different variant of PID configuration file, e.g., |
| * <code>org.ops4j.pax.url.mvn.cfg#docker</code>. |
| */ |
| @Parameter |
| private String environment; |
| |
| /** |
| * Default start level for bundles in features that don't specify it. |
| */ |
| @Parameter |
| protected int defaultStartLevel = 30; |
| |
| /** |
| * List of additional allowed protocols on bundles location URI |
| */ |
| @Parameter |
| private List<String> extraProtocols; |
| |
| /** |
| * List of compile-scope features XML files to be used in startup stage (etc/startup.properties) |
| */ |
| @Parameter |
| private List<String> startupRepositories; |
| /** |
| * List of runtime-scope features XML files to be used in boot stage (etc/org.apache.karaf.features.cfg) |
| */ |
| @Parameter |
| private List<String> bootRepositories; |
| /** |
| * List of provided-scope features XML files to be used in install stage |
| */ |
| @Parameter |
| private List<String> installedRepositories; |
| /** |
| * List of blacklisted repository URIs. Blacklisted URI may use globs and version ranges. See |
| * {@link org.apache.karaf.features.LocationPattern}. |
| */ |
| @Parameter |
| private List<String> blacklistedRepositories; |
| |
| /** |
| * List of features from compile-scope features XML files and KARs to be installed into system repo |
| * and listed in etc/startup.properties. |
| */ |
| @Parameter |
| private List<String> startupFeatures; |
| /** |
| * List of features from runtime-scope features XML files and KARs to be installed into system repo |
| * and listed in featuresBoot property in etc/org.apache.karaf.features.cfg |
| */ |
| @Parameter |
| private List<String> bootFeatures; |
| /** |
| * List of features from provided-scope features XML files and KARs to be installed into system repo |
| * and not mentioned elsewhere. |
| */ |
| @Parameter |
| private List<String> installedFeatures; |
| /** |
| * <p>List of feature blacklisting clauses. Each clause is in one of the formats ({@link org.apache.karaf.features.FeaturePattern}):<ul> |
| * <li><code>feature-name</code></li> |
| * <li><code>feature-name;range=version-or-range</code></li> |
| * <li><code>feature-name/version-or-range</code></li> |
| * </ul></p> |
| */ |
| @Parameter |
| private List<String> blacklistedFeatures; |
| |
| /** |
| * List of compile-scope bundles added to etc/startup.properties |
| */ |
| @Parameter |
| private List<String> startupBundles; |
| /** |
| * List of runtime-scope bundles wrapped in special feature added to featuresBoot property |
| * in etc/org.apache.karaf.features.cfg |
| */ |
| @Parameter |
| private List<String> bootBundles; |
| /** |
| * List of provided-scope bundles added to system repo |
| */ |
| @Parameter |
| private List<String> installedBundles; |
| /** |
| * List of blacklisted bundle URIs. Blacklisted URI may use globs and version ranges. See |
| * {@link org.apache.karaf.features.LocationPattern}. |
| */ |
| @Parameter |
| private List<String> blacklistedBundles; |
| |
| /** |
| * List of profile URIs to use |
| */ |
| @Parameter |
| private List<String> profilesUris; |
| |
| /** |
| * List of profiles names to load from configured <code>profilesUris</code> and use as startup profiles. |
| */ |
| @Parameter |
| private List<String> startupProfiles; |
| /** |
| * List of profiles names to load from configured <code>profilesUris</code> and use as boot profiles. |
| */ |
| @Parameter |
| private List<String> bootProfiles; |
| /** |
| * List of profiles names to load from configured <code>profilesUris</code> and use as installed profiles. |
| */ |
| @Parameter |
| private List<String> installedProfiles; |
| /** |
| * List of blacklisted profile names (possibly using <code>*</code> glob) |
| */ |
| @Parameter |
| private List<String> blacklistedProfiles; |
| |
| /** |
| * When assembly custom distribution, we can include generated and added profiles in the distribution itself, |
| * in <code>${karaf.etc}/profiles</code> directory. |
| */ |
| @Parameter(defaultValue = "false") |
| private boolean writeProfiles; |
| |
| /** |
| * When assembly custom distribution, we can also generate an XML/XSLT report with the summary of bundles. |
| * This parameter specifies target directory, to which <code>bundle-report.xml</code> and <code>bundle-report-full.xml</code> |
| * (along with XSLT stylesheet) will be written. |
| */ |
| @Parameter |
| private String generateConsistencyReport; |
| |
| /** |
| * When generating consistency report, we can specify project name. By default it's "Apache Karaf" |
| */ |
| @Parameter(defaultValue = "Apache Karaf") |
| private String consistencyReportProjectName; |
| |
| /** |
| * When generating consistency report, we can specify project version. By default it's "${project.version}" |
| */ |
| @Parameter(defaultValue = "${project.version}") |
| private String consistencyReportProjectVersion; |
| |
| /* |
| * KARs are not configured using Maven plugin configuration, but rather detected from dependencies. |
| * All KARs are just unzipped into the assembly being constructed, but additionally KAR's embedded |
| * features XML repositories are added to relevant stage. |
| */ |
| |
| private List<String> startupKars = new ArrayList<>(); |
| private List<String> bootKars = new ArrayList<>(); |
| private List<String> installedKars = new ArrayList<>(); |
| |
| /** |
| * TODOCUMENT |
| */ |
| @Parameter |
| private Builder.BlacklistPolicy blacklistPolicy = Builder.BlacklistPolicy.Discard; |
| |
| /** |
| * Ignore the dependency attribute (dependency="[true|false]") on bundles, effectively forcing their |
| * installation. |
| */ |
| @Parameter(defaultValue = "false") |
| protected boolean ignoreDependencyFlag; |
| |
| /** |
| * <p>Additional libraries to add into assembled distribution. Libraries are specified using |
| * <code>name[;url:=<url>][;type:=<type>][;export:=true|false][;delegate:=true|false]</code> |
| * syntax. If there's no <code>url</code> header directive, <code>name</code> is used as URI. Otherwise |
| * <code>name</code> is used as target file name to use. |
| * |
| * <p><code>type</code> may be:<ul> |
| * <li>endorsed - library will be added to <code>${karaf.home}/lib/endorsed</code></li> |
| * <li>extension - library will be added to <code>${karaf.home}/lib/ext</code></li> |
| * <li>boot - library will be added to <code>${karaf.home}/lib/boot</code></li> |
| * <li>by default, library is put directly into <code>${karaf.home}/lib</code> - these libraries will |
| * be used in default classloader for OSGi framework which will load {@link FrameworkFactory} implementation.</li> |
| * </ul> |
| * |
| * <p><code>export</code> flag determines whether packages from <code>Export-Package</code> manifest |
| * header of the library will be added to <code>org.osgi.framework.system.packages.extra</code> property in |
| * <code>${karaf.etc}/config.properties</code>. |
| * |
| * <p><code>delegate</code> flag determines whether packages from <code>Export-Pavkage</code> manifest |
| * header of the library will be added to <code>org.osgi.framework.bootdelegation</code> property in |
| * <code>${karaf.etc}/config.properties</code>. |
| */ |
| @Parameter |
| protected List<String> libraries; |
| |
| /** |
| * Use <code>reference:file:gr/oup/Id/artifactId/version/artifactId-version-classifier.type</code> style |
| * urls in <code>etc/startup.properties</code>. |
| */ |
| // see: |
| // - org.apache.felix.framework.cache.BundleArchive.createRevisionFromLocation() |
| // - org.apache.karaf.main.Main.installAndStartBundles() |
| @Parameter(defaultValue = "false") |
| protected boolean useReferenceUrls; |
| |
| /** |
| * Include project build output directory in the assembly. This allows (filtered or unfiltered) Maven |
| * resources directories to be used to provide additional resources in the assembly. |
| */ |
| @Parameter(defaultValue = "true") |
| protected boolean includeBuildOutputDirectory; |
| |
| /** |
| * Karaf version changes the way some configuration files are prepared (to adjust to given Karaf version |
| * requirements). |
| */ |
| @Parameter |
| protected Builder.KarafVersion karafVersion = Builder.KarafVersion.v4x; |
| |
| /** |
| * Specify the version of Java SE to be assumed for osgi.ee. The value will be used in |
| * <code>etc/config.properties</code> file, in <code>java.specification.version</code> placeholder used in |
| * several properties:<ul> |
| * <li><code>org.osgi.framework.system.packages</code></li> |
| * <li><code>org.osgi.framework.system.capabilities</code></li> |
| * </ul> |
| * <p>Valid values are: 1.6, 1.7, 1.8, 9 |
| */ |
| @Parameter(defaultValue = "1.8") |
| protected String javase; |
| |
| /** |
| * Specify which framework to use |
| * (one of framework, framework-logback, static-framework, static-framework-logback, custom). |
| */ |
| @Parameter |
| protected String framework; |
| |
| /** |
| * Specify an XML file that instructs this goal to apply edits to |
| * one or more standard Karaf property files. |
| * The contents of this file are documented in detail on |
| * <a href="karaf-property-instructions-model.html">this page</a>. |
| * This allows you to |
| * customize these files without making copies in your resources |
| * directories. Here's a simple example: |
| * <pre> |
| * {@literal |
| <property-edits xmlns="http://karaf.apache.org/tools/property-edits/1.0.0"> |
| <edits> |
| <edit> |
| <file>config.properties</file> |
| <operation>put</operation> |
| <key>karaf.framework</key> |
| <value>equinox</value> |
| </edit> |
| <edit> |
| <file>config.properties</file> |
| <operation>extend</operation> |
| <key>org.osgi.framework.system.capabilities</key> |
| <value>my-magic-capability</value> |
| </edit> |
| <edit> |
| <file>config.properties</file> |
| <operation prepend='true'>extend</operation> |
| <key>some-other-list</key> |
| <value>my-value-goes-first</value> |
| </edit> |
| </edits> |
| </property-edits> |
| } |
| </pre> |
| */ |
| @Parameter(defaultValue = "${project.basedir}/src/main/karaf/assembly-property-edits.xml") |
| protected String propertyFileEdits; |
| |
| /** |
| * Glob specifying which configuration PIDs in the selected boot features |
| * should be extracted to <code>${karaf.etc}</code> directory. By default all PIDs are extracted. |
| */ |
| @Parameter |
| protected List<String> pidsToExtract = Collections.singletonList("*"); |
| |
| /** |
| * Specify a set of translated urls to use instead of downloading the artifacts |
| * from their original locations. The given set will be extended with already |
| * built artifacts from the maven project. |
| */ |
| @Parameter |
| protected Map<String, String> translatedUrls; |
| |
| /** |
| * Specify a list of additional properties that should be added to <code>${karaf.etc}/config.properties</code> |
| */ |
| @Parameter |
| protected Map<String, String> config; |
| |
| /** |
| * Specify a list of additional properties that should be added to <code>${karaf.etc}/system.properties</code> |
| */ |
| @Parameter |
| protected Map<String, String> system; |
| |
| @Component(role = WorkspaceReader.class, hint = "reactor") |
| protected WorkspaceReader reactor; |
| |
| @Override |
| public void execute() throws MojoExecutionException, MojoFailureException { |
| try { |
| setNullListsToEmpty(); |
| setNullMapsToEmpty(); |
| |
| doExecute(); |
| } catch (MojoExecutionException | MojoFailureException e) { |
| throw e; |
| } catch (Exception e) { |
| throw new MojoExecutionException("Unable to build assembly", e); |
| } |
| } |
| |
| /** |
| * Main processing method. Most of the work involves configuring and invoking {@link Builder a profile builder}. |
| */ |
| protected void doExecute() throws Exception { |
| if (!startupProfiles.isEmpty() || !bootProfiles.isEmpty() || !installedProfiles.isEmpty()) { |
| if (profilesUris.size() == 0) { |
| throw new IllegalArgumentException("profilesUris option must be specified"); |
| } |
| } |
| |
| Builder builder = Builder.newInstance(); |
| |
| // Set up miscellaneous options |
| builder.extraProtocols(extraProtocols); |
| builder.offline(mavenSession.isOffline()); |
| builder.localRepository(localRepo.getBasedir()); |
| builder.resolverWrapper((resolver) -> new ReactorMavenResolver(reactor, resolver)); |
| builder.javase(javase); |
| builder.karafVersion(karafVersion); |
| builder.useReferenceUrls(useReferenceUrls); |
| builder.defaultAddAll(installAllFeaturesByDefault); |
| builder.ignoreDependencyFlag(ignoreDependencyFlag); |
| builder.propertyEdits(configurePropertyEdits()); |
| builder.translatedUrls(configureTranslatedUrls()); |
| builder.pidsToExtract(pidsToExtract); |
| builder.writeProfiles(writeProfiles); |
| builder.generateConsistencyReport(generateConsistencyReport); |
| builder.setConsistencyReportProjectName(consistencyReportProjectName); |
| builder.setConsistencyReportProjectVersion(consistencyReportProjectVersion); |
| builder.environment(environment); |
| builder.defaultStartLevel(defaultStartLevel); |
| if (featuresProcessing != null) { |
| builder.setFeaturesProcessing(featuresProcessing.toPath()); |
| } |
| |
| // Set up remote repositories from Maven build, to be used by pax-url-aether resolver |
| String remoteRepositories = MavenUtil.remoteRepositoryList(project.getRemoteProjectRepositories()); |
| getLog().info("Using repositories:"); |
| for (String r : remoteRepositories.split(",")) { |
| getLog().info(" " + r); |
| } |
| builder.mavenRepositories(remoteRepositories); |
| |
| // Set up config and system properties |
| config.forEach(builder::config); |
| system.forEach(builder::system); |
| |
| // Set up blacklisted items |
| builder.blacklistBundles(blacklistedBundles); |
| builder.blacklistFeatures(blacklistedFeatures); |
| builder.blacklistProfiles(blacklistedProfiles); |
| builder.blacklistRepositories(blacklistedRepositories); |
| builder.blacklistPolicy(blacklistPolicy); |
| |
| // Creating system directory |
| configureWorkDirectory(); |
| getLog().info("Creating work directory: " + workDirectory); |
| builder.homeDirectory(workDirectory.toPath()); |
| |
| // Loading KARs and features repositories |
| getLog().info("Loading direct KAR and features XML dependencies"); |
| processDirectMavenDependencies(); |
| |
| // Set up profiles and libraries |
| profilesUris.forEach(builder::profilesUris); |
| libraries.forEach(builder::libraries); |
| |
| // Startup stage |
| detectStartupKarsAndFeatures(builder); |
| builder.defaultStage(Builder.Stage.Startup) |
| .kars(toArray(startupKars)) |
| .repositories(startupFeatures.isEmpty() && startupProfiles.isEmpty() && installAllFeaturesByDefault, toArray(startupRepositories)) |
| .features(toArray(startupFeatures)) |
| .bundles(toArray(startupBundles)) |
| .profiles(toArray(startupProfiles)); |
| |
| // Installed stage |
| builder.defaultStage(Builder.Stage.Installed) |
| .kars(toArray(installedKars)) |
| .repositories(installedFeatures.isEmpty() && installedProfiles.isEmpty() && installAllFeaturesByDefault, toArray(installedRepositories)) |
| .features(toArray(installedFeatures)) |
| .bundles(toArray(installedBundles)) |
| .profiles(toArray(installedProfiles)); |
| |
| // Boot stage |
| builder.defaultStage(Builder.Stage.Boot) |
| .kars(toArray(bootKars)) |
| .repositories(bootFeatures.isEmpty() && bootProfiles.isEmpty() && installAllFeaturesByDefault, toArray(bootRepositories)) |
| .features(toArray(bootFeatures)) |
| .bundles(toArray(bootBundles)) |
| .profiles(toArray(bootProfiles)); |
| |
| // Generate the assembly |
| builder.generateAssembly(); |
| |
| // Include project classes content if not specified otherwise |
| if (includeBuildOutputDirectory) |
| IoUtils.copyDirectory(new File(project.getBuild().getOutputDirectory()), workDirectory); |
| |
| // Overwrite assembly dir contents with source directory (not filtered) when directory exists |
| if (sourceDirectory.exists()) |
| IoUtils.copyDirectory(sourceDirectory, workDirectory); |
| |
| // Chmod the bin/* scripts |
| File[] files = new File(workDirectory, "bin").listFiles(); |
| if (files != null) { |
| for (File file : files) { |
| if (!file.getName().endsWith(".bat")) { |
| try { |
| Files.setPosixFilePermissions(file.toPath(), PosixFilePermissions.fromString("rwxr-xr-x")); |
| } catch (Throwable ignore) { |
| // we tried our best, perhaps the OS does not support POSIX file perms. |
| } |
| } |
| } |
| } |
| } |
| |
| private void configureWorkDirectory() { |
| IoUtils.deleteRecursive(workDirectory); |
| workDirectory.mkdirs(); |
| new File(workDirectory, "etc").mkdirs(); |
| new File(workDirectory, "system").mkdirs(); |
| } |
| |
| /** |
| * <p>Turns direct maven dependencies into startup/boot/installed artifacts.</p> |
| * <p>{@link MavenProject#getDependencyArtifacts()} is deprecated, but we don't want (?) transitive |
| * dependencies given by {@link MavenProject#getArtifacts()}.</p> |
| */ |
| @SuppressWarnings("deprecation") |
| private void processDirectMavenDependencies() { |
| for (Artifact artifact : project.getDependencyArtifacts()) { |
| Builder.Stage stage = Builder.Stage.fromMavenScope(artifact.getScope()); |
| if (stage == null) { |
| continue; |
| } |
| String uri = artifactToMvn(artifact); |
| switch (getType(artifact)) { |
| case "kar": |
| addUris(stage, uri, startupKars, bootKars, installedKars); |
| break; |
| case "features": |
| addUris(stage, uri, startupRepositories, bootRepositories, installedRepositories); |
| break; |
| case "bundle": |
| addUris(stage, uri, startupBundles, bootBundles, installedBundles); |
| break; |
| } |
| } |
| } |
| |
| private void addUris(Builder.Stage stage, String uri, List<String> startup, List<String> boot, List<String> installed) { |
| switch (stage) { |
| case Startup: |
| startup.add(uri); |
| break; |
| case Boot: |
| boot.add(uri); |
| break; |
| case Installed: |
| installed.add(uri); |
| break; |
| } |
| } |
| |
| /** |
| * <p>Custom distribution is created from at least one <em>startup KAR</em> and one <em>startup</em> |
| * feature. Such startup KAR + feature is called <em>framework</em>.</p> |
| * |
| * <p>We can specify one of 5 <em>frameworks</em>:<ul> |
| * <li>framework: <code>mvn:org.apache.karaf.features/framework/VERSION/kar</code> and <code>framework</code> feature</li> |
| * <li>framework-logback: <code>mvn:org.apache.karaf.features/framework/VERSION/kar</code> and <code>framework-logback</code> feature</li> |
| * <li>static-framework: <code>mvn:org.apache.karaf.features/static/VERSION/kar</code> and <code>static-framework</code> feature</li> |
| * <li>static-framework-logback: <code>mvn:org.apache.karaf.features/static/VERSION/kar</code> and <code>static-framework-logback</code> feature</li> |
| * <li>custom: both startup KAR and startup feature has to be specified explicitly</li> |
| * </ul></p> |
| * @param builder |
| */ |
| private void detectStartupKarsAndFeatures(Builder builder) { |
| boolean hasStandardKarafFrameworkKar = false; |
| boolean hasCustomFrameworkKar = false; |
| for (Iterator<String> iterator = startupKars.iterator(); iterator.hasNext(); ) { |
| String kar = iterator.next(); |
| if (kar.startsWith("mvn:org.apache.karaf.features/framework/") |
| || kar.startsWith("mvn:org.apache.karaf.features/static/")) { |
| hasStandardKarafFrameworkKar = true; |
| iterator.remove(); |
| if (framework == null) { |
| framework = kar.startsWith("mvn:org.apache.karaf.features/framework/") |
| ? "framework" : "static-framework"; |
| } |
| getLog().info(" Standard startup Karaf KAR found: " + kar); |
| builder.kars(Builder.Stage.Startup, false, kar); |
| break; |
| } |
| } |
| |
| if (!hasStandardKarafFrameworkKar) { |
| if ("custom".equals(framework)) { |
| // we didn't detect standard Karaf KAR (framework or static), so we expect at least one |
| // other KAR dependency with compile scope and at least one startup feature |
| if (startupKars.isEmpty()) { |
| throw new IllegalArgumentException("Custom KAR was declared, but there's no Maven dependency with type=kar and scope=compile." + |
| " Please specify at least one KAR for custom assembly."); |
| } |
| if (startupFeatures.isEmpty()) { |
| throw new IllegalArgumentException("Custom KAR was declared, but there's no startup feature declared." + |
| " Please specify at least one startup feature defined in features XML repository inside custom startup KAR or startup repository."); |
| } |
| hasCustomFrameworkKar = true; |
| for (String startupKar : startupKars) { |
| getLog().info(" Custom startup KAR found: " + startupKar); |
| } |
| } else if (framework == null) { |
| throw new IllegalArgumentException("Can't determine framework to use (framework, framework-logback, static-framework, static-framework-logback, custom)." + |
| " Please specify valid \"framework\" option or add Maven dependency with \"kar\" type and \"compile\" scope for one of standard Karaf KARs."); |
| } else { |
| String realKarafVersion = Version.karafVersion(); |
| String kar; |
| switch (framework) { |
| case "framework": |
| case "framework-logback": |
| kar = "mvn:org.apache.karaf.features/framework/" + realKarafVersion + "/kar"; |
| break; |
| case "static-framework": |
| case "static-framework-logback": |
| kar = "mvn:org.apache.karaf.features/static/" + realKarafVersion + "/kar"; |
| break; |
| default: |
| throw new IllegalArgumentException("Unsupported framework: " + framework); |
| } |
| getLog().info(" Standard startup KAR implied from framework (" + framework + "): " + kar); |
| builder.kars(Builder.Stage.Startup, false, kar); |
| } |
| } |
| |
| if (hasStandardKarafFrameworkKar && !startupFeatures.contains(framework)) { |
| getLog().info(" Feature " + framework + " will be added as a startup feature"); |
| builder.features(Builder.Stage.Startup, framework); |
| } |
| } |
| |
| private KarafPropertyEdits configurePropertyEdits() throws IOException, XMLStreamException { |
| KarafPropertyEdits edits = null; |
| if (propertyFileEdits != null) { |
| File file = new File(propertyFileEdits); |
| if (file.exists()) { |
| try (InputStream editsStream = new FileInputStream(propertyFileEdits)) { |
| KarafPropertyInstructionsModelStaxReader kipmsr = new KarafPropertyInstructionsModelStaxReader(); |
| edits = kipmsr.read(editsStream, true); |
| } |
| } |
| } |
| return edits; |
| } |
| |
| private Map<String,String> configureTranslatedUrls() { |
| Map<String, String> urls = new HashMap<>(); |
| List<Artifact> artifacts = new ArrayList<>(project.getAttachedArtifacts()); |
| artifacts.add(project.getArtifact()); |
| for (Artifact artifact : artifacts) { |
| if (artifact.getFile() != null && artifact.getFile().exists()) { |
| String mvnUrl = artifactToMvn(artifact); |
| urls.put(mvnUrl, artifact.getFile().toURI().toString()); |
| } |
| } |
| urls.putAll(translatedUrls); |
| return urls; |
| } |
| |
| private String getType(Artifact artifact) { |
| // Identify kars |
| if ("kar".equals(artifact.getType())) { |
| return "kar"; |
| } |
| if ("zip".equals(artifact.getType())) { |
| try (ZipFile zip = new ZipFile(artifact.getFile())) { |
| if (zip.getEntry("META-INF/KARAF.MF") != null) { |
| return "kar"; |
| } |
| } catch (IOException e) { |
| // Ignore |
| } |
| } |
| // Identify features |
| if ("features".equals(artifact.getClassifier())) { |
| return "features"; |
| } |
| if ("xml".equals(artifact.getType())) { |
| try (InputStream is = new FileInputStream(artifact.getFile())) { |
| XMLInputFactory xif = XMLInputFactory.newFactory(); |
| xif.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, true); |
| XMLStreamReader r = xif.createXMLStreamReader(is); |
| r.nextTag(); |
| QName name = r.getName(); |
| if (name.getLocalPart().equals("features") |
| && (name.getNamespaceURI().isEmpty() |
| || name.getNamespaceURI().startsWith("http://karaf.apache.org/xmlns/features/"))) { |
| return "features"; |
| } |
| |
| } catch (Exception e) { |
| // Ignore |
| } |
| } |
| // Identify bundles |
| if ("bundle".equals(artifact.getType())) { |
| return "bundle"; |
| } |
| if ("jar".equals(artifact.getType())) { |
| try (JarFile jar = new JarFile(artifact.getFile())) { |
| Manifest manifest = jar.getManifest(); |
| if (manifest != null |
| && manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME) != null) { |
| return "bundle"; |
| } |
| } catch (Exception e) { |
| // Ignore |
| } |
| } |
| return "unknown"; |
| } |
| |
| private String artifactToMvn(Artifact artifact) { |
| String uri; |
| |
| String groupId = artifact.getGroupId(); |
| String artifactId = artifact.getArtifactId(); |
| String version = artifact.getBaseVersion(); |
| String type = artifact.getArtifactHandler().getExtension(); |
| String classifier = artifact.getClassifier(); |
| |
| if (MavenUtil.isEmpty(classifier)) { |
| if ("jar".equals(type)) { |
| uri = String.format("mvn:%s/%s/%s", groupId, artifactId, version); |
| } else { |
| uri = String.format("mvn:%s/%s/%s/%s", groupId, artifactId, version, type); |
| } |
| } else { |
| uri = String.format("mvn:%s/%s/%s/%s/%s", groupId, artifactId, version, type, classifier); |
| } |
| return uri; |
| } |
| |
| private String[] toArray(List<String> strings) { |
| return strings.toArray(new String[strings.size()]); |
| } |
| |
| private void setNullListsToEmpty() { |
| startupRepositories = nonNullList(startupRepositories); |
| bootRepositories = nonNullList(bootRepositories); |
| installedRepositories = nonNullList(installedRepositories); |
| blacklistedRepositories = nonNullList(blacklistedRepositories); |
| extraProtocols = nonNullList(extraProtocols); |
| startupBundles = nonNullList(startupBundles); |
| bootBundles = nonNullList(bootBundles); |
| installedBundles = nonNullList(installedBundles); |
| blacklistedBundles = nonNullList(blacklistedBundles); |
| startupFeatures = nonNullList(startupFeatures); |
| bootFeatures = nonNullList(bootFeatures); |
| installedFeatures = nonNullList(installedFeatures); |
| blacklistedFeatures = nonNullList(blacklistedFeatures); |
| startupProfiles = nonNullList(startupProfiles); |
| bootProfiles = nonNullList(bootProfiles); |
| installedProfiles = nonNullList(installedProfiles); |
| blacklistedProfiles = nonNullList(blacklistedProfiles); |
| libraries = nonNullList(libraries); |
| profilesUris = nonNullList(profilesUris); |
| } |
| |
| private void setNullMapsToEmpty() { |
| config = nonNullMap(config); |
| system = nonNullMap(system); |
| translatedUrls = nonNullMap(translatedUrls); |
| } |
| |
| private List<String> nonNullList(List<String> list) { |
| final List<String> nonNullList = list == null ? new ArrayList<>() : list; |
| return nonNullList.stream().filter(Objects::nonNull).collect(Collectors.toList()); |
| } |
| |
| private Map<String, String> nonNullMap(Map<String, String> map) { |
| return map == null ? new LinkedHashMap<>() : map; |
| } |
| |
| } |