You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by da...@apache.org on 2018/04/27 09:44:00 UTC

[sling-slingfeature-maven-plugin] 01/14: Move feature model to whiteboard git

This is an automated email from the ASF dual-hosted git repository.

davidb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-slingfeature-maven-plugin.git

commit 67c5f3c6396d0e2bacfafdea473cd1effa833f8d
Author: Carsten Ziegeler <cz...@adobe.com>
AuthorDate: Fri Nov 3 15:06:50 2017 +0100

    Move feature model to whiteboard git
---
 pom.xml                                            | 192 ++++++++
 .../feature/maven/ApplicationProjectConfig.java    | 116 +++++
 .../feature/maven/ApplicationProjectInfo.java      |  31 ++
 .../apache/sling/feature/maven/Environment.java    |  35 ++
 .../sling/feature/maven/FeatureConstants.java      |  32 ++
 .../sling/feature/maven/FeatureProjectConfig.java  | 138 ++++++
 .../sling/feature/maven/FeatureProjectInfo.java    |  32 ++
 .../apache/sling/feature/maven/Preprocessor.java   | 515 +++++++++++++++++++++
 .../apache/sling/feature/maven/ProjectHelper.java  | 319 +++++++++++++
 .../apache/sling/feature/maven/ProjectInfo.java    |  27 ++
 .../feature/maven/mojos/AbstractFeatureMojo.java   | 116 +++++
 .../sling/feature/maven/mojos/AttachFeature.java   |  77 +++
 .../mojos/DependencyLifecycleParticipant.java      |  89 ++++
 .../META-INF/m2e/lifecycle-mapping-metadata.xml    |  32 ++
 src/main/resources/META-INF/plexus/components.xml  | 103 +++++
 src/site/markdown/index.md                         |   6 +
 src/site/site.xml                                  |  35 ++
 17 files changed, 1895 insertions(+)

diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..0d887cf
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,192 @@
+<?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>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>32</version>
+        <relativePath />
+    </parent>
+
+    <artifactId>osgifeature-maven-plugin</artifactId>
+    <version>0.01.7-SNAPSHOT</version>
+    <packaging>maven-plugin</packaging>
+
+    <name>Apache Sling OSGi Feature Maven Plugin</name>
+    <description>
+        Maven Plugin for OSGi Applicatgions
+    </description>
+
+    <properties>
+        <sling.java.version>8</sling.java.version>
+        <maven.version>3.5.0</maven.version>
+        <maven.site.path>${project.artifactId}-archives/${project.artifactId}-LATEST</maven.site.path>
+    </properties>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/tooling/maven/osgifeature-maven-plugin</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/tooling/maven/osgifeature-maven-plugin</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/tooling/maven/osgifeature-maven-plugin</url>
+    </scm>
+
+    <!-- Support for publishing the mvn site. -->
+    <distributionManagement>
+        <site>
+            <id>apache.website</id>
+            <url>scm:svn:https://svn.apache.org/repos/infra/websites/production/sling/content/components/${maven.site.path}</url>
+        </site>
+    </distributionManagement>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.plexus</groupId>
+                <artifactId>plexus-component-metadata</artifactId>
+                <version>1.7.1</version>
+                <executions>
+                    <execution>
+                        <id>generate-metadata</id>
+                        <goals>
+                            <goal>generate-metadata</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-plugin-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>mojo-descriptor</id>
+                        <phase>process-classes</phase>
+                        <goals>
+                            <goal>descriptor</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>generated-helpmojo</id>
+                        <goals>
+                            <goal>helpmojo</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-scm-publish-plugin</artifactId>
+                <configuration>
+                    <checkoutDirectory>${user.home}/maven-sites/${maven.site.path}</checkoutDirectory>
+                    <tryUpdate>true</tryUpdate>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>src/site/markdown/**</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.feature</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.feature.support</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-core</artifactId>
+            <version>${maven.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-plugin-api</artifactId>
+            <version>${maven.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-artifact</artifactId>
+            <version>${maven.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-compat</artifactId>
+            <version>${maven.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.maven.plugin-tools</groupId>
+            <artifactId>maven-plugin-annotations</artifactId>
+            <version>3.5</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.plexus</groupId>
+            <artifactId>plexus-interactivity-api</artifactId>
+            <version>1.0-alpha-6</version>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.plexus</groupId>
+            <artifactId>plexus-archiver</artifactId>
+            <version>3.5</version>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.plexus</groupId>
+            <artifactId>plexus-utils</artifactId>
+            <version>3.0.24</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.5</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.maven.shared</groupId>
+            <artifactId>maven-filtering</artifactId>
+            <version>3.1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.10.19</version>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+    </dependencies>
+
+    <reporting>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-plugin-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </reporting>
+
+</project>
diff --git a/src/main/java/org/apache/sling/feature/maven/ApplicationProjectConfig.java b/src/main/java/org/apache/sling/feature/maven/ApplicationProjectConfig.java
new file mode 100644
index 0000000..78cd363
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/maven/ApplicationProjectConfig.java
@@ -0,0 +1,116 @@
+/*
+ * 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;
+
+import org.apache.maven.artifact.Artifact;
+
+public class ApplicationProjectConfig {
+
+    public static final String CFG_FEATURES = "features";
+
+    public static final String CFG_TEST_FEATURES = "testFeatures";
+
+    public static final String CFG_FEATURE_REFS = "featureRefs";
+
+    public static final String CFG_TEST_FEATURE_REFS = "testFeatureRefs";
+
+    public static final String DEFAULT_FEATURE_DIR = "src/main/osgi/features";
+
+    public static final String DEFAULT_TEST_FEATURE_DIR = "src/test/osgi/features";
+
+    public static final String DEFAULT_REF_DIR = "src/main/osgi/feature-refs";
+
+    public static final String DEFAULT_TEST_REF_DIR = "src/test/osgi/feature-refs";
+
+    private final String featuresDirName;
+
+    private final String featureRefsDirName;
+
+    private final boolean skipAddDep;
+
+    private final String name;
+
+    private final String scope;
+
+    private final boolean isTest;
+
+    public static ApplicationProjectConfig getMainConfig(final ApplicationProjectInfo info) {
+        return new ApplicationProjectConfig(info, false);
+    }
+
+    public static ApplicationProjectConfig getTestConfig(final ApplicationProjectInfo info) {
+        return new ApplicationProjectConfig(info, true);
+    }
+
+    private ApplicationProjectConfig(final ApplicationProjectInfo info, final boolean test) {
+        this.isTest = test;
+        final String featuresDirCfgName;
+        final String featureRefsDirCfgName;
+        final String defaultDir;
+        final String defaultRefDir;
+        final String skipAddDepCfgName;
+        final String defaultSkipValue;
+        if ( test ) {
+            featuresDirCfgName = CFG_TEST_FEATURES;
+            featureRefsDirCfgName = CFG_TEST_FEATURE_REFS;
+            defaultDir = DEFAULT_TEST_FEATURE_DIR;
+            defaultRefDir = DEFAULT_TEST_REF_DIR;
+            skipAddDepCfgName = FeatureProjectConfig.CFG_SKIP_ADD_TEST_FEATURE_DEPENDENCIES;
+            defaultSkipValue = "true";
+            this.scope = Artifact.SCOPE_TEST;
+            this.name = "test features";
+        } else {
+            featuresDirCfgName = CFG_FEATURES;
+            featureRefsDirCfgName = CFG_FEATURE_REFS;
+            defaultDir = DEFAULT_FEATURE_DIR;
+            defaultRefDir = DEFAULT_REF_DIR;
+            skipAddDepCfgName = FeatureProjectConfig.CFG_SKIP_ADD_FEATURE_DEPENDENCIES;
+            defaultSkipValue = "false";
+            this.scope = Artifact.SCOPE_PROVIDED;
+            this.name = "features";
+        }
+        this.featuresDirName = ProjectHelper.getConfigValue(info.plugin, featuresDirCfgName, defaultRefDir);
+        this.featureRefsDirName = ProjectHelper.getConfigValue(info.plugin, featureRefsDirCfgName, defaultDir);
+        final String skipCfg = ProjectHelper.getConfigValue(info.plugin, skipAddDepCfgName, defaultSkipValue);
+        this.skipAddDep = "true".equals(skipCfg.toLowerCase());
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getFeatureDir() {
+        return this.featuresDirName;
+    }
+
+    public String getFeatureRefDir() {
+        return this.featuresDirName;
+    }
+
+    public boolean isSkipAddDependencies() {
+        return this.skipAddDep;
+    }
+
+    public String getScope() {
+        return this.scope;
+    }
+
+    public boolean isTestConfig() {
+        return this.isTest;
+    }
+}
+
diff --git a/src/main/java/org/apache/sling/feature/maven/ApplicationProjectInfo.java b/src/main/java/org/apache/sling/feature/maven/ApplicationProjectInfo.java
new file mode 100644
index 0000000..dc0e393
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/maven/ApplicationProjectInfo.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+import org.apache.sling.feature.Feature;
+
+import java.util.List;
+
+public class ApplicationProjectInfo extends ProjectInfo {
+
+    public List<Feature> features;
+    public List<Feature> testFeatures;
+
+    public List<Feature> assembledFeatures;
+    public List<Feature> assembledTestFeatures;
+}
+
diff --git a/src/main/java/org/apache/sling/feature/maven/Environment.java b/src/main/java/org/apache/sling/feature/maven/Environment.java
new file mode 100644
index 0000000..ca5592d
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/maven/Environment.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
+import org.apache.maven.artifact.resolver.ArtifactResolver;
+import org.apache.maven.execution.MavenSession;
+import org.codehaus.plexus.logging.Logger;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class Environment {
+
+    public ArtifactHandlerManager artifactHandlerManager;
+    public ArtifactResolver resolver;
+    public MavenSession session;
+    public Logger logger;
+    public final Map<String, ProjectInfo> modelProjects = new HashMap<>();
+}
+
diff --git a/src/main/java/org/apache/sling/feature/maven/FeatureConstants.java b/src/main/java/org/apache/sling/feature/maven/FeatureConstants.java
new file mode 100644
index 0000000..96a3b2c
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/maven/FeatureConstants.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+public abstract class FeatureConstants {
+
+    public static final String PACKAGING_FEATURE = "osgifeature";
+
+    public static final String PACKAGING_APPLICATION = "osgiapp";
+
+    public static final String CLASSIFIER_FEATURE = "feature";
+
+    public static final String CLASSIFIER_TEST_FEATURE = "testfeature";
+
+    public static final String FEATURE_ARTIFACT_NAME = "feature.json";
+
+    public static final String TEST_FEATURE_ARTIFACT_NAME = "testfeature.json";
+}
diff --git a/src/main/java/org/apache/sling/feature/maven/FeatureProjectConfig.java b/src/main/java/org/apache/sling/feature/maven/FeatureProjectConfig.java
new file mode 100644
index 0000000..a9ba4f5
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/maven/FeatureProjectConfig.java
@@ -0,0 +1,138 @@
+/*
+ * 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;
+
+import org.apache.maven.artifact.Artifact;
+
+public class FeatureProjectConfig {
+
+    public static final String CFG_SKIP_ADD_FEATURE_DEPENDENCIES = "skipAddFeatureDependencies";
+
+    public static final String CFG_SKIP_ADD_TEST_FEATURE_DEPENDENCIES = "skipAddTestFeatureDependencies";
+
+    public static final String CFG_FEATURE_FILE = "featureFile";
+
+    public static final String CFG_TEST_FEATURE_FILE = "testFeatureFile";
+
+    public static final String CFG_FEATURE_INLINED = "feature";
+
+    public static final String CFG_TEST_FEATURE_INLINED = "testFeature";
+
+    public static final String CFG_SKIP_ADD_JAR_TO_FEATURE = "skipAddJarToFeature";
+
+    public static final String CFG_SKIP_ADD_JAR_TO_TEST_FEATURE = "skipAddJarToTestFeature";
+
+    public static final String CFG_JAR_START_LEVEL = "jarStartLevel";
+
+    public static final String DEFAULT_FEATURE_FILE = "src/main/osgi/feature.json";
+
+    public static final String DEFAULT_TEST_FEATURE_FILE = "src/test/osgi/feature.json";
+
+    public static final String DEFAULT_START_LEVEL = "20";
+
+    private final String inlinedFeature;
+
+    private final String featureFileName;
+
+    private final boolean skipAddDep;
+
+    private final String name;
+
+    private final String scope;
+
+    private final boolean isTest;
+
+    private final String jarStartLevel;
+
+    private final boolean skipAddJar;
+
+    public static FeatureProjectConfig getMainConfig(final FeatureProjectInfo info) {
+        return new FeatureProjectConfig(info, false);
+    }
+
+    public static FeatureProjectConfig getTestConfig(final FeatureProjectInfo info) {
+        return new FeatureProjectConfig(info, true);
+    }
+
+    private FeatureProjectConfig(final FeatureProjectInfo info, final boolean test) {
+        this.isTest = test;
+        final String inlineCfgName;
+        final String fileCfgName;
+        final String defaultFile;
+        final String skipAddDepCfgName;
+        final String defaultSkipValue;
+        if ( test ) {
+            inlineCfgName = CFG_TEST_FEATURE_INLINED;
+            fileCfgName = CFG_TEST_FEATURE_FILE;
+            defaultFile = DEFAULT_TEST_FEATURE_FILE;
+            this.scope = Artifact.SCOPE_TEST;
+            skipAddDepCfgName = CFG_SKIP_ADD_TEST_FEATURE_DEPENDENCIES;
+            defaultSkipValue = "true";
+            this.name = "test feature";
+            this.skipAddJar = "true".equals(ProjectHelper.getConfigValue(info.plugin, CFG_SKIP_ADD_JAR_TO_TEST_FEATURE, "true"));
+        } else {
+            inlineCfgName = CFG_FEATURE_INLINED;
+            fileCfgName = CFG_TEST_FEATURE_FILE;
+            defaultFile = DEFAULT_FEATURE_FILE;
+            this.scope = Artifact.SCOPE_PROVIDED;
+            skipAddDepCfgName = CFG_SKIP_ADD_FEATURE_DEPENDENCIES;
+            defaultSkipValue = "false";
+            this.name = "feature";
+            this.skipAddJar = "true".equals(ProjectHelper.getConfigValue(info.plugin, CFG_SKIP_ADD_JAR_TO_FEATURE, "true"));
+        }
+        this.inlinedFeature = ProjectHelper.getConfigValue(info.plugin, inlineCfgName, null);
+        this.featureFileName = ProjectHelper.getConfigValue(info.plugin, fileCfgName, defaultFile);
+        final String skipCfg = ProjectHelper.getConfigValue(info.plugin, skipAddDepCfgName, defaultSkipValue);
+        this.skipAddDep = "true".equals(skipCfg.toLowerCase());
+        this.jarStartLevel = ProjectHelper.getConfigValue(info.plugin, CFG_JAR_START_LEVEL, DEFAULT_START_LEVEL);
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getInlinedFeature() {
+        return this.inlinedFeature;
+    }
+
+    public String getFeatureFileName() {
+        return this.featureFileName;
+    }
+
+    public boolean isSkipAddDependencies() {
+        return this.skipAddDep;
+    }
+
+    public String getScope() {
+        return this.scope;
+    }
+
+    public boolean isTestConfig() {
+        return this.isTest;
+    }
+
+    public String getJarStartLevel() {
+        return this.jarStartLevel;
+    }
+
+    public boolean isSkipAddJarToFeature() {
+        return this.skipAddJar;
+    }
+
+
+}
+
diff --git a/src/main/java/org/apache/sling/feature/maven/FeatureProjectInfo.java b/src/main/java/org/apache/sling/feature/maven/FeatureProjectInfo.java
new file mode 100644
index 0000000..b0f379b
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/maven/FeatureProjectInfo.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+import org.apache.sling.feature.Feature;
+
+public class FeatureProjectInfo extends ProjectInfo {
+
+    public boolean featureDone = false;
+    public boolean testFeatureDone = false;
+
+    public Feature feature;
+    public Feature assembledFeature;
+
+    public Feature testFeature;
+    public Feature assembledTestFeature;
+}
+
diff --git a/src/main/java/org/apache/sling/feature/maven/Preprocessor.java b/src/main/java/org/apache/sling/feature/maven/Preprocessor.java
new file mode 100644
index 0000000..47a09c5
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/maven/Preprocessor.java
@@ -0,0 +1,515 @@
+/*
+ * 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;
+
+import org.apache.maven.model.Dependency;
+import org.apache.maven.project.MavenProject;
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.process.BuilderContext;
+import org.apache.sling.feature.process.FeatureBuilder;
+import org.apache.sling.feature.process.FeatureProvider;
+import org.apache.sling.feature.support.FeatureUtil;
+import org.apache.sling.feature.support.json.FeatureJSONReader;
+import org.codehaus.plexus.logging.Logger;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * The processor processes all feature projects.
+ */
+public class Preprocessor {
+
+    /**
+     * Process the provided projects.
+     * @param env The environment with all maven settings and projects
+     */
+    public void process(final Environment env) {
+        for(final ProjectInfo info : env.modelProjects.values()) {
+            if ( info instanceof FeatureProjectInfo ) {
+                final FeatureProjectInfo finfo = (FeatureProjectInfo)info;
+                process(env, finfo, FeatureProjectConfig.getMainConfig(finfo));
+                process(env, finfo, FeatureProjectConfig.getTestConfig(finfo));
+                if ( FeatureConstants.PACKAGING_FEATURE.equals(info.project.getPackaging()) && finfo.feature == null ) {
+                    throw new RuntimeException("Feature project has no feature defined: " + info.project.getId());
+                }
+
+                ProjectHelper.storeProjectInfo(finfo);
+            } else {
+                final ApplicationProjectInfo ainfo = (ApplicationProjectInfo)info;
+                process(env, ainfo, ApplicationProjectConfig.getMainConfig(ainfo));
+                process(env, ainfo, ApplicationProjectConfig.getTestConfig(ainfo));
+
+                ProjectHelper.storeProjectInfo(ainfo);
+            }
+        }
+    }
+
+    /**
+     * Process a single feature project.
+     *
+     * @param env The environment with all maven settings and projects
+     * @param info The project to process.
+     * @param config The configuration for the project.
+     */
+    private void process(final Environment env,
+            final FeatureProjectInfo info,
+            final FeatureProjectConfig config) {
+        if ( (config.isTestConfig() && info.testFeatureDone == true )
+             || (!config.isTestConfig() && info.featureDone == true) ) {
+            env.logger.debug("Return assembled " + config.getName() + " for " + info.project.getId());
+            return;
+        }
+        // prevent recursion and multiple processing
+        if ( config.isTestConfig() ) {
+            info.testFeatureDone = true;
+        } else {
+            info.featureDone = true;
+        }
+        env.logger.debug("Processing " + config.getName() + " in project " + info.project.getId());
+
+        // read project feature, either inlined or from file
+        final Feature feature = readProjectFeature(env.logger, info.project, config);
+        if ( feature == null ) {
+            env.logger.debug("No " + config.getName() + " found in project " + info.project.getId());
+            return;
+        }
+        if ( config.isTestConfig() ) {
+            info.testFeature = feature;
+        } else {
+            info.feature = feature;
+        }
+
+        // process attachments (only for jar or bundle)
+        if ( "jar".equals(info.project.getPackaging())
+             || "bundle".equals(info.project.getPackaging())) {
+            if ( config.isSkipAddJarToFeature() ) {
+                env.logger.debug("Skip adding jar to " + config.getName());
+            } else {
+                final Artifact jar = new Artifact(new ArtifactId(info.project.getGroupId(),
+                        info.project.getArtifactId(),
+                        info.project.getVersion(),
+                        null,
+                        "jar"));
+                feature.getBundles().add(Integer.valueOf(config.getJarStartLevel()), jar);
+            }
+        }
+
+        final Feature assembledFeature = FeatureBuilder.assemble(feature, new BuilderContext(this.createFeatureProvider(env,
+                info,
+                config.isTestConfig(),
+                config.isSkipAddDependencies(),
+                config.getScope(), null)));
+        if ( config.isTestConfig() ) {
+            info.assembledTestFeature = assembledFeature;
+        } else {
+            info.assembledFeature = assembledFeature;
+        }
+
+        if ( config.isSkipAddDependencies() ) {
+            env.logger.debug("Not adding artifacts from feature as dependencies");
+        } else {
+            addDependenciesFromFeature(env, info, assembledFeature, config.getScope());
+        }
+    }
+
+    private void scan(final List<File> files, final File dir, final String ext) {
+        for(final File f : dir.listFiles()) {
+            if ( !f.getName().startsWith(".") ) {
+                if ( f.isDirectory() ) {
+                    scan(files, f, ext);
+                } else if ( f.getName().endsWith("." + ext) ) {
+                    files.add(f);
+                }
+            }
+        }
+    }
+
+    /**
+     * Process a single application project.
+     *
+     * @param env The environment with all maven settings and projects
+     * @param info The project to process.
+     * @param config The configuration for the project.
+     */
+    private void process(final Environment env,
+            final ApplicationProjectInfo info,
+            final ApplicationProjectConfig config) {
+        final List<Feature> featureList = new ArrayList<>();
+        env.logger.debug("Processing " + config.getName() + " in project " + info.project.getId());
+
+        // an application supports two sets of files:
+        // features and references to features
+
+        // feature files first:
+        final File dir = new File(info.project.getBasedir(), config.getFeatureDir());
+        if ( dir.exists() ) {
+            final List<File> files = new ArrayList<>();
+            scan(files, dir, "json");
+
+            for(final File file : files) {
+                // create id in case the file does not contain one
+                // classifier is the hard part, we use the file path/name
+                String fileName = file.getAbsolutePath().substring(dir.getAbsolutePath().length() + 1);
+                fileName = fileName.substring(0, fileName.length() - 5); // remove .json
+                fileName = fileName.replace(File.separatorChar, '_');
+                fileName = fileName.replace('-', '_');
+                final String classifier;
+                if ( config.isTestConfig() ) {
+                    classifier = "test_" + fileName;
+                } else {
+                    classifier = fileName;
+                }
+                final ArtifactId id = new ArtifactId(info.project.getGroupId(),
+                        info.project.getArtifactId(),
+                        info.project.getVersion(),
+                        classifier,
+                        FeatureConstants.PACKAGING_FEATURE);
+
+                // We should pass in an "id" to FeatureJSONReader.read and later on check the id (again, need to handle ref files)
+                try (final FileReader reader = new FileReader(file)) {
+                    final Feature feature = FeatureJSONReader.read(reader, id, file.getAbsolutePath());
+
+                    this.checkFeatureId(id, feature);
+
+                    this.setProjectInfo(info.project, feature);
+                    this.postProcessReadFeature(feature);
+                    featureList.add(feature);
+
+                } catch ( final IOException io) {
+                    throw new RuntimeException("Unable to read feature " + file.getAbsolutePath(), io);
+                }
+            }
+        } else {
+            env.logger.debug("Feature directory " + config.getFeatureDir() + " does not exist in project " + info.project.getId());
+        }
+        final List<Feature> assembledFeatureList = new ArrayList<>();
+        for(final Feature feature : featureList) {
+            final Feature assembledFeature = FeatureBuilder.assemble(feature, new BuilderContext(this.createFeatureProvider(env,
+                    info,
+                    config.isTestConfig(),
+                    config.isSkipAddDependencies(),
+                    config.getScope(),
+                    featureList)));
+            assembledFeatureList.add(assembledFeature);
+        }
+        if ( config.isTestConfig() ) {
+            info.testFeatures = featureList;
+            info.assembledTestFeatures = assembledFeatureList;
+        } else {
+            info.features = featureList;
+            info.assembledFeatures = assembledFeatureList;
+        }
+
+        // and now references
+        final List<Feature> featureRefList = new ArrayList<>();
+        final File refDir = new File(info.project.getBasedir(), config.getFeatureRefDir());
+        if ( refDir.exists() ) {
+            final List<File> files = new ArrayList<>();
+            scan(files, refDir, "ref");
+
+            for(final File file : files) {
+                try {
+                    final List<String> features = FeatureUtil.parseFeatureRefFile(file);
+                    if ( features.isEmpty() ) {
+                        env.logger.debug("Empty feature ref file at " + file);
+                    } else {
+                        for(final String ref : features) {
+                            if ( !ref.startsWith("mvn:") ) {
+                                throw new RuntimeException("Unsupported feature ref in feature ref file at " + file + " : " + ref);
+                            }
+                            final ArtifactId id = ArtifactId.fromMvnUrl(ref);
+                            final Feature feature = this.createFeatureProvider(env, info, config.isTestConfig(), config.isSkipAddDependencies(), config.getScope(), null).provide(id);
+                            if ( feature == null ) {
+                                throw new RuntimeException("Unable to resolve feature " + id);
+                            }
+                            featureRefList.add(feature);
+                        }
+                    }
+                } catch ( final IOException io) {
+                    throw new RuntimeException("Unable to read feature " + file.getAbsolutePath(), io);
+                }
+            }
+        }
+        final List<Feature> assembledFeatureRefList = new ArrayList<>();
+        for(final Feature feature : featureRefList) {
+            final Feature assembledFeature = FeatureBuilder.assemble(feature, new BuilderContext(this.createFeatureProvider(env,
+                    info,
+                    config.isTestConfig(),
+                    config.isSkipAddDependencies(),
+                    config.getScope(),
+                    featureList)));
+            assembledFeatureRefList.add(assembledFeature);
+        }
+        if ( config.isTestConfig() ) {
+            info.testFeatures.addAll(featureRefList);
+            info.assembledTestFeatures.addAll(assembledFeatureRefList);
+        } else {
+            info.features.addAll(featureRefList);
+            info.assembledFeatures.addAll(assembledFeatureRefList);
+        }
+
+        if ( config.isSkipAddDependencies() ) {
+            env.logger.debug("Not adding artifacts from features as dependencies");
+        } else {
+            for(final Feature feature : assembledFeatureList) {
+                addDependenciesFromFeature(env, info, feature, config.getScope());
+            }
+        }
+    }
+
+    /**
+     * Add all dependencies from the feature
+     * @param env The environment
+     * @param info The project info
+     * @param assembledFeature The assembled feature for finding the artifacts.
+     * @param scope The scope which the new dependencies should have
+     */
+    private void addDependenciesFromFeature(
+            final Environment env,
+            final ProjectInfo info,
+            final Feature assembledFeature,
+            final String scope) {
+        for(final Map.Entry<Integer, org.apache.sling.feature.Artifact> entry : assembledFeature.getBundles()) {
+            final ArtifactId a = entry.getValue().getId();
+            if ( a.getGroupId().equals(info.project.getGroupId())
+                 && a.getArtifactId().equals(info.project.getArtifactId())
+                 && a.getVersion().equals(info.project.getVersion()) ) {
+                // skip artifact from the same project
+                env.logger.debug("- skipping dependency " + a.toMvnId());
+                continue;
+            }
+
+            env.logger.debug("- adding dependency " + a.toMvnId());
+            final Dependency dep = ProjectHelper.toDependency(a, scope);
+            info.project.getDependencies().add(dep);
+        }
+        for(final Extension ext : assembledFeature.getExtensions()) {
+            if ( ext.getType() != ExtensionType.ARTIFACTS ) {
+                continue;
+            }
+            for(final org.apache.sling.feature.Artifact art : ext.getArtifacts()) {
+                final ArtifactId a = art.getId();
+                if ( a.getGroupId().equals(info.project.getGroupId())
+                     && a.getArtifactId().equals(info.project.getArtifactId())
+                     && a.getVersion().equals(info.project.getVersion()) ) {
+                    // skip artifact from the same project
+                    env.logger.debug("- skipping dependency " + a.toMvnId());
+                    continue;
+                }
+                env.logger.debug("- adding dependency " + a.toMvnId());
+                final Dependency dep = ProjectHelper.toDependency(a, scope);
+                info.project.getDependencies().add(dep);
+            }
+        }
+    }
+
+    /**
+     * Read the feature for a feature project.
+     * The feature is either inlined in the pom or stored in a file in the project.
+     *
+     * @param logger The logger
+     * @param project The current maven project
+     * @param config The configuration
+     * @return The feature or {@code null}
+     */
+    protected Feature readProjectFeature(
+            final Logger logger,
+            final MavenProject project,
+            final FeatureProjectConfig config) {
+        final File featureFile = new File(project.getBasedir(), config.getFeatureFileName());
+        logger.debug("Checking feature file " + config.getFeatureFileName() + " : " + featureFile.exists());
+        logger.debug("Inlined feature : " + (config.getInlinedFeature() != null));
+
+        if ( config.getInlinedFeature() != null && featureFile.exists() ) {
+            throw new RuntimeException("Only one (feature file or inlined feature) can be specified - but not both");
+        }
+
+        final String classifier;
+        if ( config.isTestConfig() ) {
+            classifier = FeatureConstants.CLASSIFIER_TEST_FEATURE;
+        } else if ( FeatureConstants.PACKAGING_FEATURE.equals(project.getPackaging()) ) {
+            classifier = null;
+        } else {
+            classifier = FeatureConstants.CLASSIFIER_FEATURE;
+        }
+        final ArtifactId id = new ArtifactId(project.getGroupId(),
+                project.getArtifactId(),
+                project.getVersion(),
+                classifier,
+                FeatureConstants.PACKAGING_FEATURE);
+
+        final Feature feature;
+        if ( config.getInlinedFeature() != null ) {
+            logger.debug("Reading inlined model from project " + project.getId());
+            try (final Reader reader = new StringReader(config.getInlinedFeature())) {
+                feature = FeatureJSONReader.read(reader, id, null);
+            } catch ( final IOException io) {
+                throw new RuntimeException("Unable to read inlined feature", io);
+            }
+        } else {
+            if ( !featureFile.exists() ) {
+                logger.debug("Feature file " + featureFile + " in project " + project.getId() + " does not exist.");
+                return null;
+            }
+            logger.debug("Reading feature " + featureFile + " in project " + project.getId());
+            try (final FileReader reader = new FileReader(featureFile)) {
+                feature = FeatureJSONReader.read(reader, id, featureFile.getAbsolutePath());
+            } catch ( final IOException io) {
+                throw new RuntimeException("Unable to read feature " + featureFile, io);
+            }
+        }
+        this.checkFeatureId(id, feature);
+
+        this.setProjectInfo(project, feature);
+
+        // post process and return
+        return postProcessReadFeature(feature);
+    }
+
+    private void checkFeatureId(final ArtifactId id, final Feature feature) {
+        // check feature id
+        if ( !id.getGroupId().equals(feature.getId().getGroupId()) ) {
+            throw new RuntimeException("Wrong group id for feature. It should be " + id.getGroupId() + " but is " + feature.getId().getGroupId());
+        }
+        if ( !id.getArtifactId().equals(feature.getId().getArtifactId()) ) {
+            throw new RuntimeException("Wrong artifact id for feature. It should be " + id.getArtifactId() + " but is " + feature.getId().getArtifactId());
+        }
+        if ( !id.getVersion().equals(feature.getId().getVersion()) ) {
+            throw new RuntimeException("Wrong version for feature. It should be " + id.getVersion() + " but is " + feature.getId().getVersion());
+        }
+    }
+
+    /**
+     * Hook to post process the local feature
+     * @param result The read feature
+     * @return The post processed feature
+     */
+    protected Feature postProcessReadFeature(final Feature result)  {
+        return result;
+    }
+
+    protected void setProjectInfo(final MavenProject project, final Feature feature) {
+        // set title, description, vendor, license
+        if ( feature.getTitle() == null ) {
+            feature.setTitle(project.getName());
+        }
+        if ( feature.getDescription() == null ) {
+            feature.setDescription(project.getDescription());
+        }
+        if ( feature.getVendor() == null && project.getOrganization() != null ) {
+            feature.setVendor(project.getOrganization().getName());
+        }
+        if ( feature.getLicense() == null
+             && project.getLicenses() != null
+             && !project.getLicenses().isEmpty()) {
+            final String license = project.getLicenses().stream()
+                    .filter(l -> l.getName() != null )
+                    .map(l -> l.getName())
+                    .collect(Collectors.joining(", "));
+
+            feature.setLicense(license);
+        }
+    }
+
+    protected FeatureProvider createFeatureProvider(final Environment env,
+            final ProjectInfo info,
+            final boolean isTest,
+            final boolean skipAddDependencies,
+            final String dependencyScope,
+            final List<Feature> projectFeatures) {
+        return new FeatureProvider() {
+
+            @Override
+            public Feature provide(final ArtifactId id) {
+
+                final Dependency dep = ProjectHelper.toDependency(id, dependencyScope);
+                if ( !skipAddDependencies ) {
+
+                    env.logger.debug("- adding feature dependency " + id.toMvnId());
+                    info.project.getDependencies().add(dep);
+                }
+
+                // if it's a project from the current reactor build, we can't resolve it right now
+                final String key = id.getGroupId() + ":" + id.getArtifactId();
+                final ProjectInfo depProjectInfo = env.modelProjects.get(key);
+                if ( depProjectInfo != null ) {
+                    env.logger.debug("Found reactor " + id.getType() + " dependency to project: " + id);
+                    // check if it is a feature project
+                    if ( depProjectInfo instanceof FeatureProjectInfo ) {
+                        final FeatureProjectInfo depInfo = (FeatureProjectInfo)depProjectInfo;
+                        if ( isTest ) {
+                            process(env, depInfo, FeatureProjectConfig.getTestConfig(depInfo));
+                        } else {
+                            process(env, depInfo, FeatureProjectConfig.getMainConfig(depInfo));
+                        }
+                        if ( isTest && depInfo.assembledTestFeature == null ) {
+                            env.logger.error("Unable to get feature " + id.toMvnId() + " : Recursive test feature dependency list including project " + info.project);
+                        } else if ( !isTest && depInfo.assembledFeature == null ) {
+                            env.logger.error("Unable to get feature " + id.toMvnId() + " : Recursive feature dependency list including project " + info.project);
+                        } else {
+
+                            if ( isTest ) {
+                                return depInfo.testFeature;
+                            } else {
+                                return depInfo.feature;
+                            }
+                        }
+                    } else {
+                        // we only support a dependency to *this* application project
+                        final ApplicationProjectInfo depInfo = (ApplicationProjectInfo)depProjectInfo;
+                        if ( depInfo != info) {
+                            env.logger.error("Unable to get feature " + id.toMvnId() + " : Feature dependency is to a different application project from " + info.project);
+                            return null;
+                        }
+                        if ( projectFeatures != null ) {
+                            for(final Feature f : projectFeatures) {
+                                if ( f.getId().equals(id)) {
+                                    return f;
+                                }
+                            }
+                        }
+                        return null;
+                    }
+                } else {
+                    env.logger.debug("Found external " + id.getType() + " dependency: " + id);
+
+                    // "external" dependency, we can already resolve it
+                    final File featureFile = ProjectHelper.getOrResolveArtifact(info.project, env.session, env.artifactHandlerManager, env.resolver, id).getFile();
+                    try (final FileReader r = new FileReader(featureFile)) {
+                        return FeatureJSONReader.read(r, featureFile.getAbsolutePath());
+                    } catch ( final IOException ioe) {
+                        env.logger.error("Unable to read feature file from " + featureFile, ioe);
+                    }
+                }
+
+                return null;
+            }
+        };
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/maven/ProjectHelper.java b/src/main/java/org/apache/sling/feature/maven/ProjectHelper.java
new file mode 100644
index 0000000..a314113
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/maven/ProjectHelper.java
@@ -0,0 +1,319 @@
+/*
+ * 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;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.DefaultArtifact;
+import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
+import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
+import org.apache.maven.artifact.resolver.ArtifactResolutionException;
+import org.apache.maven.artifact.resolver.ArtifactResolver;
+import org.apache.maven.artifact.versioning.VersionRange;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Plugin;
+import org.apache.maven.model.PluginExecution;
+import org.apache.maven.project.MavenProject;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.support.json.FeatureJSONReader;
+import org.apache.sling.feature.support.json.FeatureJSONWriter;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public abstract class ProjectHelper {
+
+    /** Read feature. */
+    private static final String RAW_FEATURE_JSON = Feature.class.getName() + "/rawmain.json";
+    private static final String RAW_TEST_FEATURE_JSON = Feature.class.getName() + "/rawtest.json";
+
+    /** Assembled feature. */
+    private static final String ASSEMBLED_FEATURE_JSON = Feature.class.getName() + "/assembledmain.json";
+    private static final String ASSEMBLED_TEST_FEATURE_JSON = Feature.class.getName() + "/assembledtest.json";
+
+    private static void store(final MavenProject project, final String key, final Feature feature) {
+        if ( feature != null ) {
+            // we have to serialize as the dependency lifecycle participant uses a different class loader (!)
+            try ( final StringWriter w1 = new StringWriter() ) {
+                FeatureJSONWriter.write(w1, feature);
+                project.setContextValue(key, w1.toString());
+            } catch ( final IOException ioe) {
+                throw new RuntimeException(ioe.getMessage(), ioe);
+            }
+        }
+    }
+
+    private static void store(final MavenProject project, final String key, final List<Feature> features) {
+        if ( features != null && !features.isEmpty()) {
+            project.setContextValue(key, features.size());
+            // we have to serialize as the dependency lifecycle participant uses a different class loader (!)
+            int index = 0;
+            for(final Feature f : features) {
+                try ( final StringWriter w1 = new StringWriter() ) {
+                    FeatureJSONWriter.write(w1, f);
+                    project.setContextValue(key + "_" + String.valueOf(index), w1.toString());
+                    index++;
+                } catch ( final IOException ioe) {
+                    throw new RuntimeException(ioe.getMessage(), ioe);
+                }
+            }
+        }
+    }
+
+    private static Feature getFeature(final MavenProject project, final String key) {
+        final String cacheKey = key + "-cache";
+        Feature result = null;
+        try {
+            result = (Feature) project.getContextValue(cacheKey);
+        } catch ( final Exception e) {
+            // if we get a class cast exception, we read again
+        }
+        if ( result == null ) {
+            final String text = (String)project.getContextValue(key);
+            if ( text == null ) {
+                result = null;
+            } else {
+                try ( final StringReader r = new StringReader(text) ) {
+                    result = FeatureJSONReader.read(r, project.getId());
+                    project.setContextValue(cacheKey, result);
+                } catch ( final IOException ioe) {
+                    throw new RuntimeException(ioe.getMessage(), ioe);
+                }
+            }
+        }
+        return result;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static List<Feature> getFeatures(final MavenProject project, final String key) {
+        final String cacheKey = key + "-cache";
+        List<Feature> result = null;
+        try {
+            result = (List<Feature>) project.getContextValue(cacheKey);
+        } catch ( final Exception e) {
+            // if we get a class cast exception, we read again
+        }
+        if ( result == null ) {
+            final Integer size = (Integer)project.getContextValue(key);
+            if ( size != null ) {
+                result = new ArrayList<>();
+                for(int i=0; i<size;i++) {
+                    final String text = (String)project.getContextValue(key + "_" + String.valueOf(i));
+                    if ( text == null ) {
+                        throw new RuntimeException("Unable to get feature from internal store.");
+                    }
+                    try ( final StringReader r = new StringReader(text) ) {
+                        final Feature feature = FeatureJSONReader.read(r, project.getId());
+                        result.add(feature);
+                    } catch ( final IOException ioe) {
+                        throw new RuntimeException(ioe.getMessage(), ioe);
+                    }
+                }
+                project.setContextValue(cacheKey, result);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Store all relevant information about the project for plugins to be
+     * retrieved
+     * @param info The project info
+     */
+    public static void storeProjectInfo(final FeatureProjectInfo info) {
+        store(info.project, RAW_FEATURE_JSON, info.feature);
+        store(info.project, RAW_TEST_FEATURE_JSON, info.testFeature);
+        store(info.project, ASSEMBLED_FEATURE_JSON, info.assembledFeature);
+        store(info.project, ASSEMBLED_TEST_FEATURE_JSON, info.assembledTestFeature);
+    }
+
+    /**
+     * Get the assembled feature from the project
+     * @param project The maven projet
+     * @return The assembled feature or {@code null}
+     */
+    public static Feature getAssembledFeature(final MavenProject project) {
+        return getFeature(project, ASSEMBLED_FEATURE_JSON);
+    }
+
+    /**
+     * Get the raw feature from the project
+     * @param project The maven projet
+     * @return The raw feature or {@code null}
+     */
+    public static Feature getFeature(final MavenProject project) {
+        return getFeature(project, RAW_FEATURE_JSON);
+    }
+
+    /**
+     * Get the assembled test feature from the project
+     * @param project The maven projet
+     * @return The assembled feature or {@code null}
+     */
+    public static Feature getAssembledTestFeature(final MavenProject project) {
+        return getFeature(project, ASSEMBLED_TEST_FEATURE_JSON);
+    }
+
+    /**
+     * Get the raw test feature from the project
+     * @param project The maven projet
+     * @return The raw feature or {@code null}
+     */
+    public static Feature getTestFeature(final MavenProject project) {
+        return getFeature(project, RAW_TEST_FEATURE_JSON);
+    }
+
+    /**
+     * Store all relevant information about the project for plugins to be
+     * retrieved
+     * @param info The project info
+     */
+    public static void storeProjectInfo(final ApplicationProjectInfo info) {
+        store(info.project, RAW_FEATURE_JSON, info.features);
+        store(info.project, RAW_TEST_FEATURE_JSON, info.testFeatures);
+        store(info.project, ASSEMBLED_FEATURE_JSON, info.assembledFeatures);
+        store(info.project, ASSEMBLED_TEST_FEATURE_JSON, info.assembledTestFeatures);
+    }
+
+    /**
+     * Get the assembled features from the project
+     * @param project The maven projet
+     * @return The assembled features or {@code null}
+     */
+    public static List<Feature> getAssembledFeatures(final MavenProject project) {
+        return getFeatures(project, ASSEMBLED_FEATURE_JSON);
+    }
+
+    /**
+     * Get the raw feature from the project
+     * @param project The maven projet
+     * @return The raw features or {@code null}
+     */
+    public static List<Feature> getFeatures(final MavenProject project) {
+        return getFeatures(project, RAW_FEATURE_JSON);
+    }
+
+    /**
+     * Get the assembled test feature from the project
+     * @param project The maven projet
+     * @return The assembled features or {@code null}
+     */
+    public static List<Feature> getAssembledTestFeatures(final MavenProject project) {
+        return getFeatures(project, ASSEMBLED_TEST_FEATURE_JSON);
+    }
+
+    /**
+     * Get the raw test feature from the project
+     * @param project The maven projet
+     * @return The raw features or {@code null}
+     */
+    public static List<Feature> getTestFeatures(final MavenProject project) {
+        return getFeatures(project, RAW_TEST_FEATURE_JSON);
+    }
+
+    /**
+     * Gets a configuration value for a plugin if it is set in the configuration for
+     * the plugin or any configuration for an execution of the plugin.
+     * @param plugin Plugin
+     * @param name Configuration parameter.
+     * @param defaultValue The default value if no configuration is found.
+     * @return The default value if nothing is configured, the value otherwise.
+     * @throws RuntimeException If more than one value is configured
+     */
+    public static String getConfigValue(final Plugin plugin,
+            final String name,
+            final String defaultValue) {
+        final Set<String> values = new HashSet<>();
+        final Xpp3Dom config = plugin == null ? null : (Xpp3Dom)plugin.getConfiguration();
+        final Xpp3Dom globalNode = (config == null ? null : config.getChild(name));
+        if ( globalNode != null ) {
+            values.add(globalNode.getValue());
+        }
+        for(final PluginExecution exec : plugin.getExecutions()) {
+            final Xpp3Dom cfg = (Xpp3Dom)exec.getConfiguration();
+            final Xpp3Dom pluginNode = (cfg == null ? null : cfg.getChild(name));
+            if ( pluginNode != null ) {
+                values.add(pluginNode.getValue());
+            }
+        }
+        if ( values.size() > 1 ) {
+            throw new RuntimeException("More than one value configured in plugin (executions) of "
+                    + plugin.getKey() + " for " + name + " : " + values);
+        }
+        return values.isEmpty() ? defaultValue : values.iterator().next();
+    }
+
+    /**
+     * Get a resolved Artifact from the coordinates provided
+     *
+     * @return the artifact, which has been resolved.
+     */
+    public static Artifact getOrResolveArtifact(final MavenProject project,
+            final MavenSession session,
+            final ArtifactHandlerManager artifactHandlerManager,
+            final ArtifactResolver resolver,
+            final ArtifactId id) {
+        final Set<Artifact> artifacts = project.getDependencyArtifacts();
+        for(final Artifact artifact : artifacts) {
+            if ( artifact.getGroupId().equals(id.getGroupId())
+               && artifact.getArtifactId().equals(id.getArtifactId())
+               && artifact.getVersion().equals(id.getVersion())
+               && artifact.getType().equals(id.getVersion())
+               && ((id.getClassifier() == null && artifact.getClassifier() == null) || (id.getClassifier() != null && id.getClassifier().equals(artifact.getClassifier()))) ) {
+                return artifact;
+            }
+        }
+        final Artifact prjArtifact = new DefaultArtifact(id.getGroupId(),
+                id.getArtifactId(),
+                VersionRange.createFromVersion(id.getVersion()),
+                Artifact.SCOPE_PROVIDED,
+                id.getType(),
+                id.getClassifier(),
+                artifactHandlerManager.getArtifactHandler(id.getType()));
+        try {
+            resolver.resolve(prjArtifact, project.getRemoteArtifactRepositories(), session.getLocalRepository());
+        } catch (final ArtifactResolutionException | ArtifactNotFoundException e) {
+            throw new RuntimeException("Unable to get artifact for " + id.toMvnId(), e);
+        }
+        return prjArtifact;
+    }
+
+    public static String toString(final Dependency d) {
+        return "Dependency {groupId=" + d.getGroupId() + ", artifactId=" + d.getArtifactId() + ", version=" + d.getVersion() +
+                (d.getClassifier() != null ? ", classifier=" + d.getClassifier() : "") +
+                ", type=" + d.getType() + "}";
+    }
+
+    public static Dependency toDependency(final ArtifactId id, final String scope) {
+        final Dependency dep = new Dependency();
+        dep.setGroupId(id.getGroupId());
+        dep.setArtifactId(id.getArtifactId());
+        dep.setVersion(id.getVersion());
+        dep.setType(id.getType());
+        dep.setClassifier(id.getClassifier());
+        dep.setScope(scope);
+
+        return dep;
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/maven/ProjectInfo.java b/src/main/java/org/apache/sling/feature/maven/ProjectInfo.java
new file mode 100644
index 0000000..6e3fa0d
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/maven/ProjectInfo.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+import org.apache.maven.model.Plugin;
+import org.apache.maven.project.MavenProject;
+
+public class ProjectInfo {
+
+    public MavenProject project;
+    public Plugin       plugin;
+}
+
diff --git a/src/main/java/org/apache/sling/feature/maven/mojos/AbstractFeatureMojo.java b/src/main/java/org/apache/sling/feature/maven/mojos/AbstractFeatureMojo.java
new file mode 100644
index 0000000..7c7e2ea
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/maven/mojos/AbstractFeatureMojo.java
@@ -0,0 +1,116 @@
+/*
+ * 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.execution.MavenSession;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.MavenProjectHelper;
+import org.apache.sling.feature.maven.FeatureProjectConfig;
+
+import java.io.File;
+
+/**
+ * Base class for all mojos.
+ */
+public abstract class AbstractFeatureMojo extends AbstractMojo {
+
+    /**
+     * The feature file..
+     * This parameter is evaluated in the {@link DependencyLifecycleParticipant}.
+     */
+    @Parameter(name = FeatureProjectConfig.CFG_FEATURE_FILE,
+            defaultValue="${basedir}/" + FeatureProjectConfig.DEFAULT_FEATURE_FILE)
+    private File featureFile;
+
+    /**
+     * The test feature file..
+     * This parameter is evaluated in the {@link DependencyLifecycleParticipant}.
+     */
+    @Parameter(name = FeatureProjectConfig.CFG_TEST_FEATURE_FILE,
+            defaultValue="${basedir}/" + FeatureProjectConfig.DEFAULT_TEST_FEATURE_FILE)
+    private File testFeatureFile;
+
+    /**
+     * Inlined model.
+     * This parameter is evaluated in the {@link DependencyLifecycleParticipant}.
+     */
+    @Parameter(name = FeatureProjectConfig.CFG_FEATURE_INLINED)
+    private String feature;
+
+    /**
+     * Inlined test model.
+     * This parameter is evaluated in the {@link DependencyLifecycleParticipant}.
+     */
+    @Parameter(name = FeatureProjectConfig.CFG_TEST_FEATURE_INLINED)
+    private String testFeature;
+
+    /**
+     * If set to {@code true} the artifacts from the feature are not as dependencies to the project.
+     * This parameter is evaluated in the {@link DependencyLifecycleParticipant}.
+     */
+    @Parameter(name=FeatureProjectConfig.CFG_SKIP_ADD_FEATURE_DEPENDENCIES,
+            defaultValue="false")
+    private boolean skipAddFeatureDependencies;
+
+    /**
+     * If set to {@code true} the artifacts from the test feature are not as dependencies to the project.
+     * This parameter is evaluated in the {@link DependencyLifecycleParticipant}.
+     */
+    @Parameter(name=FeatureProjectConfig.CFG_SKIP_ADD_TEST_FEATURE_DEPENDENCIES,
+            defaultValue="true")
+    private boolean skipAddTestFeatureDependencies;
+
+    /**
+     * If set to {@code true} the main jar artifact is not added to the feature.
+     * This parameter is evaluated in the {@link DependencyLifecycleParticipant}.
+     */
+    @Parameter(name=FeatureProjectConfig.CFG_SKIP_ADD_JAR_TO_FEATURE,
+            defaultValue="false")
+    private boolean skipAddJarToFeature;
+
+    /**
+     * If set to {@code true} the main jar artifact is not added to the test feature.
+     * This parameter is evaluated in the {@link DependencyLifecycleParticipant}.
+     */
+    @Parameter(name=FeatureProjectConfig.CFG_SKIP_ADD_JAR_TO_TEST_FEATURE,
+            defaultValue="false")
+    private boolean skipAddJarToTestFeature;
+
+    /**
+     * The start level for the attached jar/bundle.
+     * This parameter is evaluated in the {@link DependencyLifecycleParticipant}.
+     */
+    @Parameter(name=FeatureProjectConfig.CFG_JAR_START_LEVEL,
+            defaultValue=FeatureProjectConfig.DEFAULT_START_LEVEL)
+    private int jarStartLevel;
+
+    @Parameter(property = "project", readonly = true, required = true)
+    protected MavenProject project;
+
+    @Parameter(property = "session", readonly = true, required = true)
+    protected MavenSession mavenSession;
+
+    @Component
+    protected MavenProjectHelper projectHelper;
+
+    protected File getTmpDir() {
+        return new File(this.project.getBuild().getDirectory(), "osgifeature-tmp");
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/maven/mojos/AttachFeature.java b/src/main/java/org/apache/sling/feature/maven/mojos/AttachFeature.java
new file mode 100644
index 0000000..79c5c23
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/maven/mojos/AttachFeature.java
@@ -0,0 +1,77 @@
+/*
+ * 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.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.maven.FeatureConstants;
+import org.apache.sling.feature.maven.ProjectHelper;
+import org.apache.sling.feature.support.json.FeatureJSONWriter;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Attach the feature as a project artifact.
+ */
+@Mojo(name = "attach-feature",
+      defaultPhase = LifecyclePhase.PACKAGE,
+      requiresDependencyResolution = ResolutionScope.TEST,
+      threadSafe = true
+    )
+public class AttachFeature extends AbstractFeatureMojo {
+
+    private void attach(final Feature feature,
+            final String artifactName,
+            final String classifier)
+    throws MojoExecutionException {
+        if ( feature != null ) {
+
+            // write the feature
+            final File outputFile = new File(this.project.getBuild().getDirectory() + File.separatorChar + artifactName);
+            outputFile.getParentFile().mkdirs();
+
+            try ( final Writer writer = new FileWriter(outputFile)) {
+                FeatureJSONWriter.write(writer, feature);
+            } catch (final IOException e) {
+                throw new MojoExecutionException("Unable to write feature to " + outputFile, e);
+            }
+
+            // if this project is a feature, it's the main artifact
+            if ( project.getPackaging().equals(FeatureConstants.PACKAGING_FEATURE)
+                 && (FeatureConstants.CLASSIFIER_FEATURE.equals(classifier))) {
+                project.getArtifact().setFile(outputFile);
+            } else {
+                // otherwise attach it as an additional artifact
+                projectHelper.attachArtifact(project, FeatureConstants.PACKAGING_FEATURE,
+                    classifier, outputFile);
+            }
+        }
+    }
+
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException {
+        attach(ProjectHelper.getFeature(this.project), FeatureConstants.FEATURE_ARTIFACT_NAME, FeatureConstants.CLASSIFIER_FEATURE);
+        attach(ProjectHelper.getTestFeature(this.project), FeatureConstants.TEST_FEATURE_ARTIFACT_NAME, FeatureConstants.CLASSIFIER_TEST_FEATURE);
+    }
+}
diff --git a/src/main/java/org/apache/sling/feature/maven/mojos/DependencyLifecycleParticipant.java b/src/main/java/org/apache/sling/feature/maven/mojos/DependencyLifecycleParticipant.java
new file mode 100644
index 0000000..ba53623
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/maven/mojos/DependencyLifecycleParticipant.java
@@ -0,0 +1,89 @@
+/*
+ * 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.AbstractMavenLifecycleParticipant;
+import org.apache.maven.MavenExecutionException;
+import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
+import org.apache.maven.artifact.resolver.ArtifactResolver;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.model.Plugin;
+import org.apache.maven.project.MavenProject;
+import org.apache.sling.feature.maven.ApplicationProjectInfo;
+import org.apache.sling.feature.maven.Environment;
+import org.apache.sling.feature.maven.FeatureConstants;
+import org.apache.sling.feature.maven.FeatureProjectInfo;
+import org.apache.sling.feature.maven.Preprocessor;
+import org.apache.sling.feature.maven.ProjectInfo;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.logging.Logger;
+
+/**
+ * Maven lifecycle participant which adds the artifacts of the model to the dependencies.
+ */
+@Component(role = AbstractMavenLifecycleParticipant.class)
+public class DependencyLifecycleParticipant extends AbstractMavenLifecycleParticipant {
+
+    /**
+     * The plugin ID consists of <code>groupId:artifactId</code>, see {@link Plugin#constructKey(String, String)}
+     */
+    private static final String PLUGIN_ID = "org.apache.sling:osgifeature-maven-plugin";
+
+    @Requirement
+    private Logger logger;
+
+    @Requirement
+    private ArtifactHandlerManager artifactHandlerManager;
+
+    /**
+     * Used to look up Artifacts in the remote repository.
+     *
+     */
+    @Requirement
+    private ArtifactResolver resolver;
+
+    @Override
+    public void afterProjectsRead(final MavenSession session) throws MavenExecutionException {
+        final Environment env = new Environment();
+        env.artifactHandlerManager = artifactHandlerManager;
+        env.resolver = resolver;
+        env.logger = logger;
+        env.session = session;
+
+        logger.debug("Searching for project using plugin '" + PLUGIN_ID + "'...");
+
+        for (final MavenProject project : session.getProjects()) {
+            // consider all projects where this plugin is configured
+            Plugin plugin = project.getPlugin(PLUGIN_ID);
+            if (plugin != null) {
+                logger.debug("Found project " + project.getId() + " using " + PLUGIN_ID);
+                final ProjectInfo info;
+                if ( FeatureConstants.PACKAGING_APPLICATION.equals(project.getPackaging()) ) {
+                    info = new ApplicationProjectInfo();
+                } else {
+                    info = new FeatureProjectInfo();
+                }
+                info.plugin = plugin;
+                info.project = project;
+                env.modelProjects.put(project.getGroupId() + ":" + project.getArtifactId(), info);
+            }
+        }
+
+        new Preprocessor().process(env);
+    }
+}
diff --git a/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml b/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml
new file mode 100644
index 0000000..0f2b325
--- /dev/null
+++ b/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml
@@ -0,0 +1,32 @@
+<!--
+ 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.
+-->
+<lifecycleMappingMetadata>
+    <pluginExecutions>
+        <pluginExecution>
+            <pluginExecutionFilter>
+                <goals>
+                    <goal>attach-feature</goal>
+                </goals>
+            </pluginExecutionFilter>
+            <action>
+                <ignore/>
+            </action>
+        </pluginExecution>
+    </pluginExecutions>
+</lifecycleMappingMetadata>
diff --git a/src/main/resources/META-INF/plexus/components.xml b/src/main/resources/META-INF/plexus/components.xml
new file mode 100644
index 0000000..6933d3b
--- /dev/null
+++ b/src/main/resources/META-INF/plexus/components.xml
@@ -0,0 +1,103 @@
+<!--
+ 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.
+-->
+<component-set>
+  <components>
+    <component>
+      <role>org.apache.maven.lifecycle.mapping.LifecycleMapping</role>
+      <role-hint>osgifeature</role-hint>
+      <implementation>org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping</implementation>
+      <configuration>
+        <lifecycles>
+          <lifecycle>
+            <id>default</id>
+            <phases>
+              <package>org.apache.sling:slingstart-maven-plugin:attach-slingfeature</package>
+              <install>org.apache.maven.plugins:maven-install-plugin:install</install>
+              <deploy>org.apache.maven.plugins:maven-deploy-plugin:deploy</deploy>
+            </phases>
+          </lifecycle>
+        </lifecycles>
+      </configuration>
+    </component>
+    <component>
+      <role>org.apache.maven.lifecycle.mapping.LifecycleMapping</role>
+      <role-hint>osgiapp</role-hint>
+      <implementation>org.apache.maven.lifecycle.mapping.DefaultLifecycleMapping</implementation>
+      <configuration>
+        <lifecycles>
+          <lifecycle>
+            <id>default</id>
+            <phases>
+              <process-resources>org.apache.maven.plugins:maven-resources-plugin:resources</process-resources>
+              <compile>org.apache.maven.plugins:maven-compiler-plugin:compile</compile>
+              <process-test-resources>
+                  org.apache.maven.plugins:maven-resources-plugin:testResources,
+              </process-test-resources>
+              <test-compile>org.apache.maven.plugins:maven-compiler-plugin:testCompile</test-compile>
+              <test>org.apache.maven.plugins:maven-surefire-plugin:test</test>
+              <prepare-package>org.apache.sling:slingstart-maven-plugin:prepare-package</prepare-package>              
+              <package>
+                  org.apache.sling:slingstart-maven-plugin:attach-slingfeature,
+                  org.apache.sling:slingstart-maven-plugin:package
+              </package>
+              <install>org.apache.maven.plugins:maven-install-plugin:install</install>
+              <deploy>org.apache.maven.plugins:maven-deploy-plugin:deploy</deploy>
+            </phases>
+          </lifecycle>
+        </lifecycles>
+      </configuration>
+    </component>
+    <component>
+      <role>org.apache.maven.artifact.handler.ArtifactHandler</role>
+      <role-hint>osgifeature</role-hint>
+      <implementation>org.apache.maven.artifact.handler.DefaultArtifactHandler</implementation>
+      <configuration>
+        <type>osgifeature</type>
+        <includesDependencies>false</includesDependencies>
+        <language>json</language>
+        <extension>json</extension>
+        <addedToClasspath>false</addedToClasspath>
+      </configuration>
+    </component>
+    <component>
+      <role>org.apache.maven.artifact.handler.ArtifactHandler</role>
+      <role-hint>osgiapp</role-hint>
+      <implementation>org.apache.maven.artifact.handler.DefaultArtifactHandler</implementation>
+      <configuration>
+        <type>osgiapp</type>
+        <includesDependencies>false</includesDependencies>
+        <language>java</language>
+        <extension>jar</extension>
+        <addedToClasspath>false</addedToClasspath>
+      </configuration>
+    </component>
+    <component>
+      <role>org.apache.maven.artifact.handler.ArtifactHandler</role>
+      <role-hint>osgijar</role-hint>
+      <implementation>org.apache.maven.artifact.handler.DefaultArtifactHandler</implementation>
+      <configuration>
+        <type>osgijar</type>
+        <includesDependencies>false</includesDependencies>
+        <language>java</language>
+        <extension>zip</extension>
+        <addedToClasspath>false</addedToClasspath>
+      </configuration>
+    </component>
+  </components>
+</component-set>
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
new file mode 100644
index 0000000..bd08e61
--- /dev/null
+++ b/src/site/markdown/index.md
@@ -0,0 +1,6 @@
+OSGiMaven Plugin
+================
+
+Maven Plugin for OSGi Applications
+
+See [Goals](plugin-info.html) for a list of supported goals.
diff --git a/src/site/site.xml b/src/site/site.xml
new file mode 100644
index 0000000..9abab11
--- /dev/null
+++ b/src/site/site.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+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>
+  <skin>
+    <groupId>org.apache.maven.skins</groupId>
+    <artifactId>maven-fluido-skin</artifactId>
+    <version>1.6</version>
+  </skin>
+  <body>
+    <menu name="Overview">
+      <item name="Introduction" href="index.html"/>
+      <item name="Goals" href="plugin-info.html"/>
+    </menu>
+    <menu ref="reports"/>
+  </body>
+</project>

-- 
To stop receiving notification emails like this one, please contact
davidb@apache.org.