You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by an...@apache.org on 2019/12/03 20:32:47 UTC
[sling-slingfeature-maven-plugin] 01/01: Created new Goal to create
an FM Descriptor from its POM
This is an automated email from the ASF dual-hosted git repository.
andysch pushed a commit to branch feature/create-fm-descriptor
in repository https://gitbox.apache.org/repos/asf/sling-slingfeature-maven-plugin.git
commit f0870054dbf72d926c9f9288dc2e68b99f511a48
Author: Andreas Schaefer <sc...@iMac.local>
AuthorDate: Tue Dec 3 12:30:45 2019 -0800
Created new Goal to create an FM Descriptor from its POM
and install it in the local Maven repo for other modules to pick up with includeArtifact
---
README.md | 37 +++
pom.xml | 6 +
.../create-fm-descriptor-simple/invoker.properties | 17 ++
src/it/create-fm-descriptor-simple/pom.xml | 67 +++++
src/it/create-fm-descriptor-simple/verify.bsh | 74 ++++++
.../maven/mojos/CreateFeatureDescriptorMojo.java | 144 ++++++++++
.../mojos/manager/DefaultFeaturesManager.java | 294 +++++++++++++++++++++
.../maven/mojos/manager/FeaturesManager.java | 42 +++
.../feature/maven/mojos/manager/RunmodeMapper.java | 75 ++++++
.../mojos/manager/SimpleVariablesInterpolator.java | 52 ++++
.../maven/mojos/manager/VariablesInterpolator.java | 25 ++
11 files changed, 833 insertions(+)
diff --git a/README.md b/README.md
index 1bfb2d2..bd46cbd 100644
--- a/README.md
+++ b/README.md
@@ -423,6 +423,43 @@ Beside the Feature Files this Mojo for now supports all the parameters
of the current Feature Launcher (1.0.7-SNAPSHOT). For more info see the
FeautreLaucherMojoTest.testFullLaunch() test method.
+## Create Feature Model Descriptor from POM (create-fm-descriptor)
+
+A simple Maven module creates its artifact but in order to another module
+to use that in the context of a Feature Model build the module's Feature
+Module descriptor must be created during the build.
+This can be done here:
+```
+<!-- Generate and Install the Sling OSGi Feature Model file -->
+<plugin>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>slingfeature-maven-plugin</artifactId>
+ <version>${slingfeature-maven-plugin.version}</version>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <id>create-fm-descriptor</id>
+ <phase>package</phase>
+ <goals>
+ <goal>create-fm-descriptor</goal>
+ </goals>
+ <configuration>
+ <classifier>jcr-packageinit</classifier>
+ </configuration>
+ </execution>
+ </executions>
+</plugin>
+```
+The property **classifier** will be added only to the Feature Model
+Descriptor file name when it is installed in your local Maven repository.
+If one is provided then the user of that descriptor needs to specify it
+in the **includeArtifact**.
+The Feature Model Descriptor file name in the local Maven repository has
+this pattern:
+```
+<group id>:<artifact id>-<version>=[-<classifier>].slingosgifeature
+```
+
## Features Diff (features-diff)
This MOJO compares different versions of the same Feature Model, producing the prototype
diff --git a/pom.xml b/pom.xml
index b3f13fe..ceefa86 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,6 +36,7 @@
<maven.version>3.5.0</maven.version>
<maven.scm.version>1.11.1</maven.scm.version>
<maven.site.path>${project.artifactId}-archives/${project.artifactId}-LATEST</maven.site.path>
+ <maven-artifact-transfer.version>0.11.0</maven-artifact-transfer.version>
</properties>
<scm>
@@ -310,6 +311,11 @@
<version>3.6.0</version>
</dependency>
<dependency>
+ <groupId>org.apache.maven.shared</groupId>
+ <artifactId>maven-artifact-transfer</artifactId>
+ <version>${maven-artifact-transfer.version}</version>
+ </dependency>
+ <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>1.3</version>
diff --git a/src/it/create-fm-descriptor-simple/invoker.properties b/src/it/create-fm-descriptor-simple/invoker.properties
new file mode 100644
index 0000000..209b6ef
--- /dev/null
+++ b/src/it/create-fm-descriptor-simple/invoker.properties
@@ -0,0 +1,17 @@
+# 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.
+
+invoker.goals = clean install
+invoker.debug = true
diff --git a/src/it/create-fm-descriptor-simple/pom.xml b/src/it/create-fm-descriptor-simple/pom.xml
new file mode 100644
index 0000000..5516676
--- /dev/null
+++ b/src/it/create-fm-descriptor-simple/pom.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>slingfeature-maven-plugin-test-create-fm-descriptor-simple</artifactId>
+ <packaging>jar</packaging>
+ <version>1.0.0-SNAPSHOT</version>
+
+ <name>Apache Sling Features Maven plugin test - create fm descriptor simple</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.janino</groupId>
+ <artifactId>janino</artifactId>
+ <version>2.7.5</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.ant</groupId>
+ <artifactId>ant-nodeps</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.testing</artifactId>
+ <version>2.1.2</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>@project.groupId@</groupId>
+ <artifactId>@project.artifactId@</artifactId>
+ <version>@project.version@</version>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <id>analyze</id>
+ <phase>package</phase>
+ <goals>
+ <goal>create-fm-descriptor</goal>
+ </goals>
+ <configuration>
+ <classifier>test</classifier>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/src/it/create-fm-descriptor-simple/verify.bsh b/src/it/create-fm-descriptor-simple/verify.bsh
new file mode 100644
index 0000000..c5d8872
--- /dev/null
+++ b/src/it/create-fm-descriptor-simple/verify.bsh
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+import java.io.*;
+import java.util.*;
+import org.codehaus.plexus.util.*;
+
+ boolean check() {
+ // Check folder and file existence
+
+ String group = "org.apache.sling";
+ String groupPath = group.replaceAll("\\.", "/");
+ String artifact = "slingfeature-maven-plugin-test-create-fm-descriptor-simple";
+ String version = "1.0.0-SNAPSHOT";
+ File localMavenRepositoryInstallationFolder = new File(
+ localRepositoryPath, groupPath + "/" + artifact + "/" + version
+ );
+ if(!localMavenRepositoryInstallationFolder.exists()) {
+ System.out.println("Installation Folder does not exist: " + localMavenRepositoryInstallationFolder);
+ return false;
+ }
+
+ String classifier = "test";
+ String extension = "slingosgifeature";
+ File fmDescriptorFile = new File(
+ localMavenRepositoryInstallationFolder, artifact + "-" + version + "-" + classifier + "." + extension
+ );
+ if(!fmDescriptorFile.exists()) {
+ System.out.println("FM Descriptor file does not exist: " + fmDescriptorFile);
+ return false;
+ }
+
+ // Check FM Descriptor Content
+ String fmContent = FileUtils.fileRead(fmDescriptorFile);
+ System.out.println("FM Descriptor File Content: " + fmContent);
+ String dependentGroup = "org.codehaus.janino";
+ String dependentArtifact = "janino";
+ String dependentVersion = "2.7.5";
+ String[] values = {
+ "\"id\":\"" + group + ":" + artifact + ":slingosgifeature:" + version + "\"",
+ "\"bundles\":[",
+ "\"id\":\"" + group + ":" + artifact + ":" + version + "\"",
+ "\"id\":\"" + dependentGroup + ":" + dependentArtifact + ":" + dependentVersion + "\"",
+ };
+ for (String value : values) {
+ if (fmContent.indexOf(value) < 0) {
+ System.out.println("Did not find line: " + value + " -> FAILED!");
+ return false;
+ }
+ }
+
+ return true;
+ }
+ try {
+ return check();
+ }
+ catch(Throwable t) {
+ t.printStackTrace();
+ return false;
+ }
+ return true;
diff --git a/src/main/java/org/apache/sling/feature/maven/mojos/CreateFeatureDescriptorMojo.java b/src/main/java/org/apache/sling/feature/maven/mojos/CreateFeatureDescriptorMojo.java
new file mode 100644
index 0000000..21537af
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/maven/mojos/CreateFeatureDescriptorMojo.java
@@ -0,0 +1,144 @@
+/*
+ * 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.sling.feature.maven.mojos;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.DefaultArtifact;
+import org.apache.maven.artifact.handler.DefaultArtifactHandler;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.shared.transfer.artifact.install.ArtifactInstaller;
+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.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.ProjectBuildingRequest;
+import org.apache.maven.shared.transfer.artifact.install.ArtifactInstallerException;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.maven.mojos.manager.DefaultFeaturesManager;
+import org.apache.sling.feature.maven.mojos.manager.FeaturesManager;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Create a Feature Model Descriptor (slingosigfeature) based on the
+ * project's POM
+ */
+@Mojo(
+ name = "create-fm-descriptor",
+ requiresProject = true,
+ threadSafe = true
+)
+public class CreateFeatureDescriptorMojo extends AbstractIncludingFeatureMojo {
+
+ public static final String CFG_VERBOSE = "verbose";
+ public static final String CFG_CLASSIFIER = "classifier";
+
+ public static final String CFG_FM_OUTPUT_DIRECTORY = "featureModelsOutputDirectory";
+ public static final String DEFAULT_FM_OUTPUT_DIRECTORY = "${project.build.directory}/fm";
+
+ /**
+ * Target directory for the Feature Model file
+ */
+ @Parameter(property = CFG_FM_OUTPUT_DIRECTORY, defaultValue = DEFAULT_FM_OUTPUT_DIRECTORY)
+ private File fmOutput;
+
+ /**
+ * Optional Classifier for this Feature Model Descriptor file name
+ */
+ @Parameter(property = CFG_CLASSIFIER, required = false, defaultValue = "")
+ private String classifier;
+
+ /**
+ * Framework Properties for the Launcher
+ */
+ @Parameter(property = CFG_VERBOSE, required = false, defaultValue = "false")
+ private boolean verbose;
+
+ @Component
+ protected ArtifactInstaller installer;
+
+ @Override
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ checkPreconditions();
+ // Create a Feature Model Descriptor with its bundle in it
+ FeaturesManager featuresManager = new DefaultFeaturesManager(
+ true, 20, fmOutput, null, null, null
+ );
+ featuresManager.init(project.getGroupId(), project.getArtifactId(), project.getVersion());
+ ArtifactId bundle = new ArtifactId(project.getGroupId(), project.getArtifactId(), project.getVersion(), "", "bundle");
+ featuresManager.addArtifact("", bundle);
+ List<Dependency> dependencies = project.getDependencies();
+ for(Dependency dependency: dependencies) {
+ if("compile".equals(dependency.getScope())) {
+ featuresManager.addArtifact("", new ArtifactId(
+ dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), dependency.getClassifier(), dependency.getType()
+ ));
+ }
+ }
+ getLog().info("Project Features: " + featuresManager);
+ try {
+ featuresManager.serialize();
+ // Now install the file as .slingosigfeature into the local Maven Repo
+ installFMDescriptor(bundle);
+ } catch(Exception e) {
+ getLog().error("Failure to serialize Feature Manager", e);
+ }
+ }
+ private void installFMDescriptor(ArtifactId artifact) {
+ Collection<Artifact> artifacts = Collections.synchronizedCollection(new ArrayList<>());
+ // Source FM Descriptor File Path
+ String fmDescriptorFilePath = artifact.getArtifactId() + ".json";
+ File fmDescriptorFile = new File(fmOutput, fmDescriptorFilePath);
+ if(fmDescriptorFile.exists() && fmDescriptorFile.canRead()) {
+ // Need to create a new Artifact Handler for the different extension and an Artifact to not
+ // change the module artifact
+ DefaultArtifactHandler fmArtifactHandler = new DefaultArtifactHandler("slingosgifeature");
+ //AS TODO: For now the classifier is not set (check if we need to add the artifact id)
+ DefaultArtifact fmArtifact = new DefaultArtifact(
+ artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion(),
+ null, "slingosgifeature", classifier, fmArtifactHandler
+ );
+ fmArtifact.setFile(fmDescriptorFile);
+ artifacts.add(fmArtifact);
+ try {
+ installArtifact(mavenSession.getProjectBuildingRequest(), artifacts);
+ } catch (MojoFailureException | MojoExecutionException e) {
+ getLog().error("Failed to install FM Descriptor", e);
+ }
+ } else {
+ getLog().error("Could not find FM Descriptor File: " + fmDescriptorFile);
+ }
+ }
+
+ private void installArtifact(ProjectBuildingRequest pbr, Collection<Artifact> artifacts )
+ throws MojoFailureException, MojoExecutionException
+ {
+ try
+ {
+ installer.install(pbr, artifacts);
+ }
+ catch ( ArtifactInstallerException e )
+ {
+ throw new MojoExecutionException( "ArtifactInstallerException", e );
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/maven/mojos/manager/DefaultFeaturesManager.java b/src/main/java/org/apache/sling/feature/maven/mojos/manager/DefaultFeaturesManager.java
new file mode 100644
index 0000000..f283aaa
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/maven/mojos/manager/DefaultFeaturesManager.java
@@ -0,0 +1,294 @@
+/*
+ * 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.sling.feature.maven.mojos.manager;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Artifacts;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.Extensions;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.io.json.FeatureJSONWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import static java.util.Objects.requireNonNull;
+
+public class DefaultFeaturesManager implements FeaturesManager {
+
+ public static final String ZIP_TYPE = "zip";
+
+ private static final String JAVA_IO_TMPDIR_PROPERTY = "java.io.tmpdir";
+
+ private static final String CONTENT_PACKAGES = "content-packages";
+
+ private static final String SLING_OSGI_FEATURE_TILE_TYPE = "slingosgifeature";
+
+ private static final String JSON_FILE_EXTENSION = ".json";
+
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+
+ private final Map<String, Feature> runModes = new HashMap<>();
+
+ private final VariablesInterpolator interpolator = new SimpleVariablesInterpolator();
+
+ private final boolean mergeConfigurations;
+
+ private final int bundlesStartOrder;
+
+ private final File featureModelsOutputDirectory;
+
+ private final String artifactIdOverride;
+
+ private final String prefix;
+
+ private final Map<String, String> properties;
+
+ private final List<String> targetAPIRegions = new ArrayList<>();
+
+ private Feature targetFeature = null;
+
+ public DefaultFeaturesManager() {
+ this(true, 20, new File(System.getProperty(JAVA_IO_TMPDIR_PROPERTY)), null, null, null);
+ }
+
+ public DefaultFeaturesManager(boolean mergeConfigurations,
+ int bundlesStartOrder,
+ File featureModelsOutputDirectory,
+ String artifactIdOverride,
+ String prefix,
+ Map<String, String> properties) {
+ this.mergeConfigurations = mergeConfigurations;
+ this.bundlesStartOrder = bundlesStartOrder;
+ this.featureModelsOutputDirectory = featureModelsOutputDirectory;
+ this.artifactIdOverride = artifactIdOverride;
+ this.prefix = prefix;
+ this.properties = properties;
+ }
+
+ public void init(String groupId, String artifactId, String version) {
+ targetFeature = new Feature(new ArtifactId(groupId, artifactId, version, null, SLING_OSGI_FEATURE_TILE_TYPE));
+
+ initAPIRegions(targetFeature);
+
+ runModes.clear();
+ }
+
+ private void initAPIRegions(Feature feature) {
+ if (targetAPIRegions.size() > 0) {
+ Extension apiRegions = new Extension(ExtensionType.JSON, "api-regions", false);
+ StringBuilder jsonBuilder = new StringBuilder("[");
+ for (String apiRegion : targetAPIRegions) {
+ if (jsonBuilder.length() > 1) {
+ jsonBuilder.append(',');
+ }
+ jsonBuilder.append("{\"name\":\"");
+ jsonBuilder.append(apiRegion);
+ jsonBuilder.append("\",\"exports\":[]}");
+ }
+ jsonBuilder.append("]");
+ apiRegions.setJSON(jsonBuilder.toString());
+ feature.getExtensions().add(apiRegions);
+ }
+ }
+
+ public Feature getTargetFeature() {
+ return targetFeature;
+ }
+
+ public Feature getRunMode(String runMode) {
+ if (getTargetFeature() == null) {
+ throw new IllegalStateException("Target Feature not initialized yet, please make sure convert() method was invoked first.");
+ }
+
+ if (runMode == null) {
+ return getTargetFeature();
+ }
+
+ ArtifactId newId = appendRunmode(getTargetFeature().getId(), runMode);
+
+ return runModes.computeIfAbsent(runMode, k -> {
+ Feature f = new Feature(newId);
+ initAPIRegions(f);
+ return f;
+ });
+ }
+
+ public void addArtifact(String runMode, ArtifactId id) {
+ addArtifact(runMode, id, null);
+ }
+
+ public void addArtifact(String runMode, ArtifactId id, Integer startOrder) {
+ requireNonNull(id, "Artifact can not be attached to a feature without specifying a valid ArtifactId.");
+
+ Artifact artifact = new Artifact(id);
+
+ Feature targetFeature = getRunMode(runMode);
+ Artifacts artifacts;
+
+ if (ZIP_TYPE.equals(id.getType()) ) {
+ Extensions extensions = targetFeature.getExtensions();
+ Extension extension = extensions.getByName(CONTENT_PACKAGES);
+
+ if (extension == null) {
+ extension = new Extension(ExtensionType.ARTIFACTS, CONTENT_PACKAGES, true);
+ extensions.add(extension);
+ }
+
+ artifacts = extension.getArtifacts();
+ } else {
+ int startOrderForBundle = startOrder != null ? startOrder.intValue() : bundlesStartOrder;
+ artifact.setStartOrder(startOrderForBundle);
+ artifacts = targetFeature.getBundles();
+ }
+
+ artifacts.add(artifact);
+ }
+
+ private ArtifactId appendRunmode(ArtifactId id, String runMode) {
+ ArtifactId newId;
+ if (runMode == null) {
+ newId = id;
+ } else {
+ final String classifier;
+ if (id.getClassifier() != null && !id.getClassifier().isEmpty()) {
+ classifier = id.getClassifier() + '-' + runMode;
+ } else {
+ classifier = runMode;
+ }
+
+ newId = new ArtifactId(id.getGroupId(), id.getArtifactId(), id.getVersion(), classifier, id.getType());
+ }
+ return newId;
+ }
+
+ public void addConfiguration(String runMode, String pid, Dictionary<String, Object> configurationProperties) {
+ Feature feature = getRunMode(runMode);
+ Configuration configuration = feature.getConfigurations().getConfiguration(pid);
+
+ if (configuration == null) {
+ configuration = new Configuration(pid);
+ feature.getConfigurations().add(configuration);
+ } else if (!mergeConfigurations) {
+ throw new IllegalStateException("Configuration '"
+ + pid
+ + "' already defined in Feature Model '"
+ + feature.getId().toMvnId()
+ + "', set the 'mergeConfigurations' flag to 'true' if you want to merge multiple configurations with same PID");
+ }
+
+ Enumeration<String> keys = configurationProperties.keys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ Object value = configurationProperties.get(key);
+
+ if (value != null && Collection.class.isInstance(value)) {
+ value = ((Collection<?>) value).toArray();
+ }
+
+ configuration.getProperties().put(key, value);
+ }
+ }
+
+ public void serialize() throws Exception {
+ RunmodeMapper runmodeMapper = RunmodeMapper.open(featureModelsOutputDirectory);
+
+ serialize(targetFeature, null, runmodeMapper);
+
+ if (!runModes.isEmpty()) {
+ for (Entry<String, Feature> runmodeEntry : runModes.entrySet()) {
+ String runmode = runmodeEntry.getKey();
+ serialize(runmodeEntry.getValue(), runmode, runmodeMapper);
+ }
+ }
+
+ runmodeMapper.save();
+ }
+
+ private void serialize(Feature feature, String runMode, RunmodeMapper runmodeMapper) throws Exception {
+ StringBuilder fileNameBuilder = new StringBuilder()
+ .append((prefix != null) ? prefix : "")
+ .append(feature.getId().getArtifactId());
+
+ String classifier = feature.getId().getClassifier();
+ if (classifier != null && !classifier.isEmpty()) {
+ fileNameBuilder.append('-').append(classifier);
+ }
+
+ if (properties != null) {
+ properties.put("filename", fileNameBuilder.toString());
+ }
+
+ fileNameBuilder.append(JSON_FILE_EXTENSION);
+
+ String fileName = fileNameBuilder.toString();
+
+ File targetFile = new File(featureModelsOutputDirectory, fileName);
+ if (!targetFile.getParentFile().exists()) {
+ targetFile.getParentFile().mkdirs();
+ }
+
+ if (artifactIdOverride != null && !artifactIdOverride.isEmpty()) {
+ String interpolatedIdOverride = interpolator.interpolate(artifactIdOverride, properties);
+ ArtifactId idOverrride = appendRunmode(ArtifactId.parse(interpolatedIdOverride), runMode);
+ feature = feature.copy(idOverrride);
+ }
+
+ logger.info("Writing resulting Feature Model '{}' to file '{}'...", feature.getId(), targetFile);
+
+ try (FileWriter targetWriter = new FileWriter(targetFile)) {
+ FeatureJSONWriter.write(targetWriter, feature);
+
+ logger.info("'{}' Feature File successfully written!", targetFile);
+
+ runmodeMapper.addOrUpdate(runMode, fileName);
+ }
+ }
+
+ public synchronized DefaultFeaturesManager setAPIRegions(List<String> regions) {
+ targetAPIRegions.clear();
+ targetAPIRegions.addAll(regions);
+ return this;
+ }
+
+ @Override
+ public void addOrAppendRepoInitExtension(String text) {
+
+ Extension repoInitExtension = getTargetFeature().getExtensions().getByName(Extension.EXTENSION_NAME_REPOINIT);
+
+ if (repoInitExtension == null) {
+ repoInitExtension = new Extension(ExtensionType.TEXT, Extension.EXTENSION_NAME_REPOINIT, true);
+ getTargetFeature().getExtensions().add(repoInitExtension);
+ repoInitExtension.setText(text);
+ } else {
+ repoInitExtension.setText(repoInitExtension.getText() + "\n " + text);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/maven/mojos/manager/FeaturesManager.java b/src/main/java/org/apache/sling/feature/maven/mojos/manager/FeaturesManager.java
new file mode 100644
index 0000000..ac7d1a2
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/maven/mojos/manager/FeaturesManager.java
@@ -0,0 +1,42 @@
+/*
+ * 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.sling.feature.maven.mojos.manager;
+
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Feature;
+
+import java.util.Dictionary;
+
+public interface FeaturesManager {
+
+ void init(String groupId, String artifactId, String version);
+
+ Feature getTargetFeature();
+
+ Feature getRunMode(String runMode);
+
+ void addArtifact(String runMode, ArtifactId id);
+
+ void addArtifact(String runMode, ArtifactId id, Integer startOrder);
+
+ void addConfiguration(String runMode, String pid, Dictionary<String, Object> configurationProperties);
+
+ void serialize() throws Exception;
+
+ void addOrAppendRepoInitExtension(String text);
+
+}
diff --git a/src/main/java/org/apache/sling/feature/maven/mojos/manager/RunmodeMapper.java b/src/main/java/org/apache/sling/feature/maven/mojos/manager/RunmodeMapper.java
new file mode 100644
index 0000000..eb161e3
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/maven/mojos/manager/RunmodeMapper.java
@@ -0,0 +1,75 @@
+/*
+ * 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.sling.feature.maven.mojos.manager;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+final class RunmodeMapper {
+
+ private static final String FILENAME = "runmode.mapping";
+
+ public static RunmodeMapper open(File featureModelsOutputDirectory) throws IOException {
+ Properties properties = new Properties();
+
+ File runmodeMappingFile = new File(featureModelsOutputDirectory, FILENAME);
+ if (runmodeMappingFile.exists()) {
+ try (FileInputStream input = new FileInputStream(runmodeMappingFile)) {
+ properties.load(input);
+ }
+ }
+
+ return new RunmodeMapper(runmodeMappingFile, properties);
+ }
+
+ private static final String DEFAULT = "(default)";
+
+ private final File runmodeMappingFile;
+
+ private final Properties properties;
+
+ private RunmodeMapper(File runmodeMappingFile, Properties properties) {
+ this.runmodeMappingFile = runmodeMappingFile;
+ this.properties = properties;
+ }
+
+ public void addOrUpdate(String runMode, String jsonFileName) {
+ if (runMode == null) {
+ runMode = DEFAULT;
+ }
+
+ String value = properties.getProperty(runMode);
+
+ if (value != null && !value.contains(jsonFileName)) {
+ value += ',' + jsonFileName;
+ } else {
+ value = jsonFileName;
+ }
+
+ properties.setProperty(runMode, value);
+ }
+
+ public void save() throws IOException {
+ try (FileOutputStream output = new FileOutputStream(runmodeMappingFile)) {
+ properties.store(output, "File edited by the Apache Sling Content Package to Sling Feature converter");
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/maven/mojos/manager/SimpleVariablesInterpolator.java b/src/main/java/org/apache/sling/feature/maven/mojos/manager/SimpleVariablesInterpolator.java
new file mode 100644
index 0000000..a9acc56
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/maven/mojos/manager/SimpleVariablesInterpolator.java
@@ -0,0 +1,52 @@
+/*
+ * 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.sling.feature.maven.mojos.manager;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static java.util.Objects.requireNonNull;
+
+public final class SimpleVariablesInterpolator implements VariablesInterpolator {
+
+ private final Pattern replacementPattern = Pattern.compile("\\$\\{\\{(.+?)\\}\\}");
+
+ public String interpolate(String format, Map<String, String> properties) {
+ requireNonNull(format, "Input string format must be not null");
+
+ if (properties == null || properties.isEmpty()) {
+ return format;
+ }
+
+ Matcher matcher = replacementPattern.matcher(format);
+ StringBuffer result = new StringBuffer();
+ while (matcher.find()) {
+ String variable = matcher.group(1);
+ String resolved = properties.get(variable);
+
+ if (resolved == null) {
+ resolved = String.format("\\$\\{\\{%s\\}\\}", variable);
+ }
+
+ matcher.appendReplacement(result, resolved);
+ }
+ matcher.appendTail(result);
+ return result.toString();
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/feature/maven/mojos/manager/VariablesInterpolator.java b/src/main/java/org/apache/sling/feature/maven/mojos/manager/VariablesInterpolator.java
new file mode 100644
index 0000000..0759967
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/maven/mojos/manager/VariablesInterpolator.java
@@ -0,0 +1,25 @@
+/*
+ * 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.sling.feature.maven.mojos.manager;
+
+import java.util.Map;
+
+public interface VariablesInterpolator {
+
+ String interpolate(String format, Map<String, String> properties);
+
+}